From 5b179784f522f614cdadb0e4a82de410f9b3e6cd Mon Sep 17 00:00:00 2001 From: Jeromy Cannon Date: Mon, 7 Aug 2023 13:08:18 -0500 Subject: [PATCH] fix: correct KeyValuePair equals and add coverage for ServiceLocator (#257) Signed-off-by: Nathan Klick Signed-off-by: Jeromy Cannon Co-authored-by: Nathan Klick --- .../base/api/collections/KeyValuePair.java | 2 +- .../test/collections/KeyValuePairTest.java | 34 ++++++++++ .../api/test/reflect/ReflectionUtilsTest.java | 60 ++++++++++++++++++ .../api/test/resource/ResourceLoaderTest.java | 52 +++++++++++++++ .../src/test/resources/resource.txt | 1 + fullstack-bom/build.gradle.kts | 2 +- .../service/locator/api/ArtifactLoader.java | 2 +- .../locator/test/api/ArtifactLoaderTest.java | 60 +++++++++++++++++- .../locator/test/api/ServiceLocatorTest.java | 13 ++++ .../locator/test/mock/MockSlf4jLocator.java | 5 ++ .../src/test/java/module-info.java | 1 + .../src/test/resources/modules/text.jar | Bin 0 -> 480 bytes .../src/test/resources/no-modules/.gitkeep | 0 13 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/collections/KeyValuePairTest.java create mode 100644 fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/reflect/ReflectionUtilsTest.java create mode 100644 fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/resource/ResourceLoaderTest.java create mode 100644 fullstack-base-api/src/test/resources/resource.txt create mode 100644 fullstack-service-locator/src/test/resources/modules/text.jar create mode 100644 fullstack-service-locator/src/test/resources/no-modules/.gitkeep diff --git a/fullstack-base-api/src/main/java/com/hedera/fullstack/base/api/collections/KeyValuePair.java b/fullstack-base-api/src/main/java/com/hedera/fullstack/base/api/collections/KeyValuePair.java index 53f49f261..e49f7d010 100644 --- a/fullstack-base-api/src/main/java/com/hedera/fullstack/base/api/collections/KeyValuePair.java +++ b/fullstack-base-api/src/main/java/com/hedera/fullstack/base/api/collections/KeyValuePair.java @@ -47,7 +47,7 @@ public static KeyValuePair of(final K key, final V value) { public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof KeyValuePair that)) return false; - return Objects.equals(key, that.key); + return Objects.equals(key, that.key) && Objects.equals(value, that.value); } @Override diff --git a/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/collections/KeyValuePairTest.java b/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/collections/KeyValuePairTest.java new file mode 100644 index 000000000..73dc610d3 --- /dev/null +++ b/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/collections/KeyValuePairTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.fullstack.base.api.test.collections; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hedera.fullstack.base.api.collections.KeyValuePair; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class KeyValuePairTest { + @Test + @DisplayName("Test KeyValuePair") + void testKeyValuePair() { + KeyValuePair kvp1 = new KeyValuePair<>("key", "value"); + KeyValuePair kvp1v2 = new KeyValuePair<>("key", "value2"); + assertThat(kvp1).isNotEqualTo(kvp1v2); + assertThat(kvp1.hashCode()).isEqualTo(kvp1v2.hashCode()); + } +} diff --git a/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/reflect/ReflectionUtilsTest.java b/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/reflect/ReflectionUtilsTest.java new file mode 100644 index 000000000..ee82a5a30 --- /dev/null +++ b/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/reflect/ReflectionUtilsTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.fullstack.base.api.test.reflect; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Named.named; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.hedera.fullstack.base.api.reflect.ReflectionUtils; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ReflectionUtilsTest { + static Stream primitiveAndWrapperSupplier() { + return Stream.of( + arguments(named("void.class", void.class), named("Void.class", Void.class)), + arguments(named("boolean.class", boolean.class), named("Boolean.class", Boolean.class)), + arguments(named("byte.class", byte.class), named("Byte.class", Byte.class)), + arguments(named("char.class", char.class), named("Character.class", Character.class)), + arguments(named("short.class", short.class), named("Short.class", Short.class)), + arguments(named("int.class", int.class), named("Integer.class", Integer.class)), + arguments(named("long.class", long.class), named("Long.class", Long.class)), + arguments(named("float.class", float.class), named("Float.class", Float.class)), + arguments(named("double.class", double.class), named("Double.class", Double.class)), + arguments(named("String.class", String.class), named("String.class", String.class))); + } + + @ParameterizedTest(name = "Validate wrapper for {0}") + @MethodSource("primitiveAndWrapperSupplier") + @DisplayName("Test primitive as wrapper class") + void testPrimitiveAsWrapperClass(Class primitiveClass, Class wrapperClass) { + Class result = ReflectionUtils.primitiveAsWrapperClass(primitiveClass); + assertThat(result).isEqualTo(wrapperClass); + } + + @ParameterizedTest(name = "Validate primitive for {1}") + @MethodSource("primitiveAndWrapperSupplier") + @DisplayName("Test wrapper as primitive class") + void testWrapperAsPrimitiveClass(Class primitiveClass, Class wrapperClass) { + Class result = ReflectionUtils.wrapperAsPrimitiveClass(wrapperClass); + assertThat(result).isEqualTo(primitiveClass); + } +} diff --git a/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/resource/ResourceLoaderTest.java b/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/resource/ResourceLoaderTest.java new file mode 100644 index 000000000..80c7f7206 --- /dev/null +++ b/fullstack-base-api/src/test/java/com/hedera/fullstack/base/api/test/resource/ResourceLoaderTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.fullstack.base.api.test.resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.hedera.fullstack.base.api.resource.ResourceLoader; +import java.io.IOException; +import java.nio.file.Path; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ResourceLoaderTest { + @Test + @DisplayName("Verify load works as expected") + void testLoad() throws IOException { + ResourceLoader resourceLoader = new ResourceLoader<>(ResourceLoaderTest.class); + Path resourcePath = resourceLoader.load("resource.txt"); + assertThat(resourcePath) + .exists() + .isRegularFile() + .hasFileName("resource.txt") + .isWritable() + .isReadable() + .isExecutable(); + } + + @Test + @DisplayName("Verify load fails when called with a non-existent file") + void testLoadIOException() { + ResourceLoader resourceLoader = new ResourceLoader<>(ResourceLoaderTest.class); + assertThatThrownBy(() -> resourceLoader.load("not-resource.txt")).isInstanceOf(IOException.class); + } +} diff --git a/fullstack-base-api/src/test/resources/resource.txt b/fullstack-base-api/src/test/resources/resource.txt new file mode 100644 index 000000000..2125dcd86 --- /dev/null +++ b/fullstack-base-api/src/test/resources/resource.txt @@ -0,0 +1 @@ +This is some text for testing purposes. \ No newline at end of file diff --git a/fullstack-bom/build.gradle.kts b/fullstack-bom/build.gradle.kts index 40abf5408..b0d3c4478 100644 --- a/fullstack-bom/build.gradle.kts +++ b/fullstack-bom/build.gradle.kts @@ -41,7 +41,7 @@ dependencies.constraints { api("org.slf4j:slf4j-api:2.0.7") api("org.slf4j:slf4j-nop:2.0.7") api("org.slf4j:slf4j-simple:2.0.7") - api("com.jcovalent.junit:jcovalent-junit-logging:0.2.0") + api("com.jcovalent.junit:jcovalent-junit-logging:0.3.0") api("io.github.classgraph:classgraph:4.8.161") for (p in rootProject.childProjects) { diff --git a/fullstack-service-locator/src/main/java/com/hedera/fullstack/service/locator/api/ArtifactLoader.java b/fullstack-service-locator/src/main/java/com/hedera/fullstack/service/locator/api/ArtifactLoader.java index 9dce1a812..1380a3d8f 100644 --- a/fullstack-service-locator/src/main/java/com/hedera/fullstack/service/locator/api/ArtifactLoader.java +++ b/fullstack-service-locator/src/main/java/com/hedera/fullstack/service/locator/api/ArtifactLoader.java @@ -338,7 +338,7 @@ private void loadModules(final ModuleLayer parentLayer) { final ModuleFinder finder = ModuleFinder.of(modulePath.toArray(new Path[0])); try { final Configuration cfg = - parentLayer.configuration().resolve(finder, ModuleFinder.of(), Collections.emptySet()); + parentLayer.configuration().resolveAndBind(finder, ModuleFinder.of(), Collections.emptySet()); moduleLayer = parentLayer.defineModulesWithOneLoader(cfg, classLoader); } catch (LayerInstantiationException | SecurityException e) { LOGGER.atError().setCause(e).log("Failed to instantiate module layer, unable to load module path entries"); diff --git a/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ArtifactLoaderTest.java b/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ArtifactLoaderTest.java index 858c362bf..779419d6c 100644 --- a/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ArtifactLoaderTest.java +++ b/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ArtifactLoaderTest.java @@ -17,17 +17,28 @@ package com.hedera.fullstack.service.locator.test.api; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.hedera.fullstack.base.api.resource.ResourceLoader; import com.hedera.fullstack.service.locator.api.ArtifactLoader; +import com.hedera.fullstack.service.locator.api.ServiceLocator; +import com.hedera.fullstack.service.locator.test.mock.MockSlf4jLocator; +import com.jcovalent.junit.logging.JCovalentLoggingSupport; +import com.jcovalent.junit.logging.LogEntryBuilder; +import com.jcovalent.junit.logging.LoggingOutput; +import com.jcovalent.junit.logging.assertj.LoggingOutputAssert; import java.io.IOException; import java.net.URLClassLoader; import java.nio.file.Path; +import java.util.List; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.slf4j.event.Level; +import org.slf4j.spi.SLF4JServiceProvider; @DisplayName("Artifact Loader") +@JCovalentLoggingSupport class ArtifactLoaderTest { private static final ResourceLoader RESOURCE_LOADER = @@ -42,7 +53,7 @@ static void setup() throws IOException { } @Test - @DisplayName("Logback: Artifacts dynamically loaded") + @DisplayName("Logback: Artifacts dynamically loaded successfully") void logbackDynamicLoading() { final ArtifactLoader artifactLoader = ArtifactLoader.from(JAR_PATH); assertThat(artifactLoader).isNotNull(); @@ -51,4 +62,51 @@ void logbackDynamicLoading() { assertThat(artifactLoader.classPath()).isNotEmpty(); assertThat(artifactLoader.modulePath()).isNotEmpty(); } + + @Test + @DisplayName("Logback: Artifacts dynamically loaded can be used by Service Loader") + void logbackDynamicServiceLoading() { + final ArtifactLoader artifactLoader = ArtifactLoader.from(JAR_PATH); + assertServiceLocatorLoadedCorrectly(artifactLoader); + } + + @Test + @DisplayName("Logback: Artifacts dynamically loaded with an empty directory") + void logbackDynamicServiceLoadingEmptyDirectory(final LoggingOutput loggingOutput) { + final ArtifactLoader artifactLoader = ArtifactLoader.from(Path.of("no-modules")); + assertThat(artifactLoader).isNotNull(); + LoggingOutputAssert.assertThat(loggingOutput) + .hasAtLeastOneEntry(List.of(LogEntryBuilder.builder() + .level(Level.DEBUG) + .message("No module path entries found, skipping module layer creation") + .build())); + } + + @Test + @DisplayName("Logback: Artifacts dynamically recursive folder loaded can be used by Service Loader") + void logbackDynamicServiceRecursiveFolderLoading() { + final ArtifactLoader artifactLoader = ArtifactLoader.from(true, Path.of(".")); + assertServiceLocatorLoadedCorrectly(artifactLoader); + } + + private void assertServiceLocatorLoadedCorrectly(ArtifactLoader artifactLoader) { + assertThat(artifactLoader).isNotNull(); + + final ServiceLocator serviceLocator = MockSlf4jLocator.create(artifactLoader); + assertThat(serviceLocator).isNotNull(); + + final SLF4JServiceProvider serviceProvider = serviceLocator.findFirst().orElseThrow(); + assertThat(serviceProvider) + .isNotNull() + .extracting(Object::getClass) + .extracting(Class::getName) + .isEqualTo("ch.qos.logback.classic.spi.LogbackServiceProvider"); + } + + @Test + @DisplayName("Logback: Artifacts load fails with no paths") + void logbackDynamicServiceLoadingWithNoPaths(final LoggingOutput loggingOutput) { + Path[] emptyPaths = new Path[0]; + assertThatThrownBy(() -> ArtifactLoader.from(null, emptyPaths)).isInstanceOf(IllegalArgumentException.class); + } } diff --git a/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ServiceLocatorTest.java b/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ServiceLocatorTest.java index 02e9ffefa..5f4e97547 100644 --- a/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ServiceLocatorTest.java +++ b/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/api/ServiceLocatorTest.java @@ -17,11 +17,14 @@ package com.hedera.fullstack.service.locator.test.api; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.hedera.fullstack.service.locator.api.ServiceLocator; +import com.hedera.fullstack.service.locator.api.ServiceSupplier; import com.hedera.fullstack.service.locator.test.mock.CtorService; import com.hedera.fullstack.service.locator.test.mock.MockCtorService; import com.hedera.fullstack.service.locator.test.mock.MockLocator; +import java.util.Iterator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -75,4 +78,14 @@ void locatorHasWorkingReloadSupport() { locator.reload(); assertThat(locator.stream().count()).isEqualTo(2); } + + @Test + @DisplayName("CtorService: Locator's iterator removes entry") + void locatorIteratorRemovesEntry() { + final ServiceLocator locator = MockLocator.create(); + assertThat(locator).isNotNull(); + assertThat(locator.stream().count()).isEqualTo(2); + Iterator> iterator = locator.iterator(); + assertThatThrownBy(iterator::remove).isInstanceOf(UnsupportedOperationException.class); + } } diff --git a/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/mock/MockSlf4jLocator.java b/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/mock/MockSlf4jLocator.java index c54946fef..cb6085c57 100644 --- a/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/mock/MockSlf4jLocator.java +++ b/fullstack-service-locator/src/test/java/com/hedera/fullstack/service/locator/test/mock/MockSlf4jLocator.java @@ -16,6 +16,7 @@ package com.hedera.fullstack.service.locator.test.mock; +import com.hedera.fullstack.service.locator.api.ArtifactLoader; import com.hedera.fullstack.service.locator.api.ServiceLocator; import java.util.ServiceLoader; import org.slf4j.spi.SLF4JServiceProvider; @@ -28,4 +29,8 @@ private MockSlf4jLocator(ServiceLoader serviceLoader) { public static ServiceLocator create() { return new MockSlf4jLocator(ServiceLoader.load(SLF4JServiceProvider.class)); } + + public static ServiceLocator create(final ArtifactLoader loader) { + return new MockSlf4jLocator(ServiceLoader.load(loader.moduleLayer(), SLF4JServiceProvider.class)); + } } diff --git a/fullstack-service-locator/src/test/java/module-info.java b/fullstack-service-locator/src/test/java/module-info.java index 4c33a9a33..6ca6b4241 100644 --- a/fullstack-service-locator/src/test/java/module-info.java +++ b/fullstack-service-locator/src/test/java/module-info.java @@ -15,6 +15,7 @@ requires com.hedera.fullstack.base.api; requires com.hedera.fullstack.service.locator; + requires com.jcovalent.junit.logging; requires org.assertj.core; requires org.junit.jupiter.api; requires org.slf4j; diff --git a/fullstack-service-locator/src/test/resources/modules/text.jar b/fullstack-service-locator/src/test/resources/modules/text.jar new file mode 100644 index 0000000000000000000000000000000000000000..80f65eb440500fefaffcd822bef48a2942225626 GIT binary patch literal 480 zcmWIWW@h1H;9y{2P_1JR2U2jr!JxoUl3G!sS5i?D8p6xKp2l)91%^v2xEUB(zA-W| zu!sP41bDN9jDUjxps6B2Q#nvfy#tgN2AUcl@9XI79~_~t7mw9~VxUE679=N#CZ{JP zC4BJp3H!hw))COaG(mz{T|k=gqm}~O$M*&TtTN6{O$&S`^l3W0X40WCT%wH!B;+5GEkp4y1*F Ik;A|M03x$({Qv*} literal 0 HcmV?d00001 diff --git a/fullstack-service-locator/src/test/resources/no-modules/.gitkeep b/fullstack-service-locator/src/test/resources/no-modules/.gitkeep new file mode 100644 index 000000000..e69de29bb