From babc91415ed3666581deb866b19c1809a495e189 Mon Sep 17 00:00:00 2001 From: Ashley Scopes <73482956+ascopes@users.noreply.github.com> Date: Sun, 5 Feb 2023 14:04:39 +0000 Subject: [PATCH] Start writing ContainerGroupRepositoryImpl tests --- .../impl/ContainerGroupRepositoryImpl.java | 5 +- .../ascopes/jct/tests/helpers/Fixtures.java | 18 +- .../ContainerGroupRepositoryImplTest.java | 259 ++++++++++++++++++ .../unit/containers/impl/package-info.java | 19 ++ .../tests/unit/containers/package-info.java | 19 ++ 5 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/ContainerGroupRepositoryImplTest.java create mode 100644 java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/package-info.java create mode 100644 java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/package-info.java diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupRepositoryImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupRepositoryImpl.java index d5d7532c2..0c0d2c74d 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupRepositoryImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupRepositoryImpl.java @@ -354,9 +354,8 @@ private void addModuleRoot(Location location, PathRoot pathRoot) { if (modules.isEmpty()) { LOGGER.warn( - "There were no valid modules present in {}, so nothing has been registered. " - + "If you are sure that there are files, this is probably a bug.", - modules + "There were no valid modules present in {}, so nothing has been registered.", + pathRoot.getPath() ); } else { var moduleGroup = getOrCreateModuleContainerGroup(location); diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/helpers/Fixtures.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/helpers/Fixtures.java index 7fd12763d..ebc458d31 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/helpers/Fixtures.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/helpers/Fixtures.java @@ -57,7 +57,6 @@ * * @author Ashley Scopes */ -@SuppressWarnings("NullableProblems") public final class Fixtures { private static final Random RANDOM = new Random(); @@ -369,7 +368,22 @@ public static Location someLocation() { * @return some mock path root object. */ public static PathRoot somePathRoot() { - return mock(PathRoot.class, "PathRoot-" + someText()); + var name = "PathRoot-" + someText(); + var path = someRelativePath().resolve(name); + PathRoot pathRoot = mock(withSettings() + .strictness(Strictness.LENIENT) + .name(path.toString())); + when(pathRoot.getPath()).thenReturn(path); + when(pathRoot.getUri()).thenReturn(path.toUri()); + + try { + when(pathRoot.getUrl()).thenReturn(path.toUri().toURL()); + } catch (Exception ex) { + throw new IllegalStateException("unreachable", ex); + } + + when(pathRoot.getParent()).thenReturn(null); + return pathRoot; } /** diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/ContainerGroupRepositoryImplTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/ContainerGroupRepositoryImplTest.java new file mode 100644 index 000000000..abd30808a --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/ContainerGroupRepositoryImplTest.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2022 - 2023, the original author or authors. + * + * 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 io.github.ascopes.jct.tests.unit.containers.impl; + +import static io.github.ascopes.jct.tests.helpers.Fixtures.somePathRoot; +import static io.github.ascopes.jct.tests.helpers.Fixtures.someRelease; +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.list; +import static org.mockito.ArgumentMatchers.any; + +import io.github.ascopes.jct.containers.Container; +import io.github.ascopes.jct.containers.PackageContainerGroup; +import io.github.ascopes.jct.containers.impl.ContainerGroupRepositoryImpl; +import io.github.ascopes.jct.filemanagers.ModuleLocation; +import io.github.ascopes.jct.utils.ModuleDiscoverer; +import io.github.ascopes.jct.workspaces.PathRoot; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; +import javax.tools.StandardLocation; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * {@link ContainerGroupRepositoryImpl} tests. + * + *

This is partially an integration test in nature, but this is only done since there is + * little benefit in mocking all moving components. Components that perform things like module + * discovery will still be mocked internally. The idea behind this class is to ensure that the + * implementation performs the expected behaviour as a pure black box (i.e. an opaque implementation + * that may be subject to structural change over time). + * + * @author Ashley Scopes + */ +@DisplayName("ContainerGroupRepositoryImpl tests") +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({"AssertBetweenInconvertibleTypes", "resource"}) +class ContainerGroupRepositoryImplTest { + + String release; + ContainerGroupRepositoryImpl repository; + + @Mock(answer = Answers.RETURNS_SMART_NULLS) + MockedStatic moduleDiscoverer; + + @BeforeEach + void setUp() { + release = someRelease(); + repository = new ContainerGroupRepositoryImpl(release); + } + + @AfterEach + void tearDown() { + repository.close(); + } + + @DisplayName("Initialising the repository sets the release") + @Test + void initialisingTheRepositorySetsTheRelease() { + // Given + var release = someRelease(); + try (var repository = new ContainerGroupRepositoryImpl(release)) { + // Then + assertThat(repository.getRelease()).isEqualTo(release); + } + } + + @DisplayName(".addPath(...) tests") + @Nested + class AddPathTest { + + @DisplayName("adding a module location in a non-existent parent registers the module correctly") + @Test + void addingModuleLocationInNonExistentParentRegistersModuleCorrectly() { + // Given + var moduleLocation = new ModuleLocation(StandardLocation.MODULE_SOURCE_PATH, "foo.bar"); + PathRoot pathRoot = somePathRoot(); + + // When + repository.addPath(moduleLocation, pathRoot); + + // Then + var groups = repository.getModuleContainerGroups(); + assertThat(groups) + .as("module oriented groups") + .hasSize(1); + + var group = groups.iterator().next(); + assertThat(group.getLocation()) + .as("module oriented group location") + .isEqualTo(StandardLocation.MODULE_SOURCE_PATH); + + var modules = group.getModules(); + assertThat(modules) + .as("map of modules in module oriented group") + .hasSize(1) + .containsKey(moduleLocation); + + var module = modules.get(moduleLocation); + assertThat(module.getLocation()) + .as("module group location") + .isEqualTo(moduleLocation); + + var packages = module.getPackages(); + assertThat(packages) + .as("collection of module group packages") + .hasSize(1); + + var modulePackage = packages.iterator().next(); + assertThat(modulePackage.getPathRoot()) + .as("module group package path root") + .isEqualTo(pathRoot); + } + + @DisplayName("adding a package to a non-existent output group registers it as a package") + @Test + void addingPackageToNonExistentOutputGroupRegistersItAsPackage() { + // Given + var location = StandardLocation.CLASS_OUTPUT; + PathRoot pathRoot = somePathRoot(); + + // When + repository.addPath(location, pathRoot); + + // Then + var groups = repository.getOutputContainerGroups(); + assertThat(groups) + .as("output container groups") + .hasSize(1); + + var group = groups.iterator().next(); + assertThat(group.getLocation()) + .as("output container group location") + .isEqualTo(location); + + assertThat(group.getModules()) + .as("output container group modules") + .isEmpty(); + + var packages = group.getPackages(); + assertThat(packages) + .as("output container group packages") + .hasSize(1); + + var outputPackage = packages.iterator().next(); + assertThat(outputPackage.getPathRoot()) + .as("output package path root") + .isEqualTo(pathRoot); + } + + @DisplayName( + "adding an empty directory to a module-oriented group does not register any modules" + ) + @Test + void addingEmptyDirectoryToModuleOrientedGroupDoesNotRegisterModules() { + // Given + var location = StandardLocation.MODULE_SOURCE_PATH; + PathRoot pathRoot = somePathRoot(); + + moduleDiscoverer.when(() -> ModuleDiscoverer.findModulesIn(any())) + .thenReturn(Map.of()); + + // When + repository.addPath(location, pathRoot); + + // Then + var groups = repository.getModuleContainerGroups(); + assertThat(groups) + .as("module container groups") + .isEmpty(); + + moduleDiscoverer.verify(() -> ModuleDiscoverer.findModulesIn(pathRoot.getPath())); + } + + @DisplayName( + "adding a directory of modules to a module-oriented group registers them as modules" + ) + @Test + void addingDirectoryOfModulesToModuleOrientedGroupRegistersThemAsModules() { + // Given + var location = StandardLocation.MODULE_SOURCE_PATH; + PathRoot pathRoot = somePathRoot(); + + var discoveredModules = Stream.of("foo.bar", "baz.bork") + .collect(toMap( + Function.identity(), + pathRoot.getPath()::resolve + )); + + moduleDiscoverer.when(() -> ModuleDiscoverer.findModulesIn(any())) + .thenReturn(discoveredModules); + + // When + repository.addPath(location, pathRoot); + + // Then + var groups = repository.getModuleContainerGroups(); + assertThat(groups) + .as("module container groups") + .hasSize(1); + + var group = groups.iterator().next(); + assertThat(group.getLocation()) + .as("module container group location") + .isEqualTo(location); + + var modules = group.getModules(); + assertThat(modules) + .as("module container group modules") + .hasSize(2); + + assertThat(modules.get(new ModuleLocation(StandardLocation.MODULE_SOURCE_PATH, "foo.bar"))) + .as("module container group module ") + .extracting(PackageContainerGroup::getPackages, list(Container.class)) + .singleElement() + .extracting(Container::getPathRoot) + .extracting(PathRoot::getPath) + .isEqualTo(pathRoot.getPath().resolve("foo.bar")); + + assertThat(modules.get(new ModuleLocation(StandardLocation.MODULE_SOURCE_PATH, "baz.bork"))) + .as("module container group module ") + .extracting(PackageContainerGroup::getPackages, list(Container.class)) + .singleElement() + .extracting(Container::getPathRoot) + .extracting(PathRoot::getPath) + .isEqualTo(pathRoot.getPath().resolve("baz.bork")); + + moduleDiscoverer.verify(() -> ModuleDiscoverer.findModulesIn(pathRoot.getPath())); + } + + @DisplayName("adding a package root registers the package root") + @Test + void addingPackageRootRegistersPackageRoot() { + + } + } +} diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/package-info.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/package-info.java new file mode 100644 index 000000000..9351d3aac --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/impl/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 - 2023, the original author or authors. + * + * 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. + */ +@NullMarked +package io.github.ascopes.jct.tests.unit.containers.impl; + +import org.jspecify.annotations.NullMarked; diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/package-info.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/package-info.java new file mode 100644 index 000000000..12e74719e --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/containers/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 - 2023, the original author or authors. + * + * 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. + */ +@NullMarked +package io.github.ascopes.jct.tests.unit.containers; + +import org.jspecify.annotations.NullMarked;