From 9b116064250acab2430968cda7514879dfa5593a Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Tue, 8 Oct 2024 11:10:21 +0200 Subject: [PATCH] GH-861 - JUnit change detection now properly detects changes in current working directory. Added a decorating FileModificationDetector implementation that filters changes to only return those that affect the current working directory, so that the detection works properly if executed from a nested folder in multi-module projects. --- .../junit/diff/FileModificationDetector.java | 4 + .../modulith/junit/diff/ModifiedFile.java | 17 ++++ .../diff/WorkingDirectoryChangesDetector.java | 82 +++++++++++++++++++ .../FileModificationDetectorUnitTests.java | 6 +- .../junit/diff/ModifiedFileUnitTest.java | 43 ++++++++++ ...kingDirectoryChangesDetectorUnitTests.java | 51 ++++++++++++ 6 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetector.java create mode 100644 spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ModifiedFileUnitTest.java create mode 100644 spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetectorUnitTests.java diff --git a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/FileModificationDetector.java b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/FileModificationDetector.java index f6e32ff7..1f9234b1 100644 --- a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/FileModificationDetector.java +++ b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/FileModificationDetector.java @@ -52,6 +52,10 @@ public interface FileModificationDetector { * @return will never be {@literal null}. */ public static FileModificationDetector getDetector(PropertyResolver propertyResolver) { + return WorkingDirectoryChangesDetector.of(getTargetDetector(propertyResolver)); + } + + static FileModificationDetector getTargetDetector(PropertyResolver propertyResolver) { Assert.notNull(propertyResolver, "PropertyResolver must not be null!"); diff --git a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/ModifiedFile.java b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/ModifiedFile.java index d758b580..eee772a8 100644 --- a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/ModifiedFile.java +++ b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/ModifiedFile.java @@ -18,6 +18,7 @@ package org.springframework.modulith.junit.diff; import java.util.Arrays; import java.util.stream.Stream; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -39,4 +40,20 @@ public record ModifiedFile(String path) { public static Stream of(String... paths) { return Arrays.stream(paths).map(ModifiedFile::new); } + + /** + * Returns the current {@link ModifiedFile} as relative to the given reference path. I.e., a {@code foo/bar.txt} with + * a reference of {@code foo} would result in {@code bar.txt}. + * + * @param reference must not be {@literal null}. + * @return will never be {@literal null}. + */ + ModifiedFile asRelativeTo(String reference) { + + Assert.notNull(reference, "Path must not be null!"); + Assert.isTrue(reference.startsWith(reference), + () -> "Modified file at %s is not located in %s!".formatted(reference, reference)); + + return reference.isEmpty() ? this : new ModifiedFile(this.path.substring(reference.length() + 1)); + } } diff --git a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetector.java b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetector.java new file mode 100644 index 00000000..d2df2d7f --- /dev/null +++ b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetector.java @@ -0,0 +1,82 @@ +/* + * Copyright 2024 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 + * + * https://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 org.springframework.modulith.junit.diff; + +import java.io.File; +import java.util.stream.Stream; + +import org.springframework.util.Assert; + +/** + * A {@link FileModificationDetector} that filters the {@link ModifiedFile} instances returned by a delegate + * {@link FileModificationDetector} to only contain those nested in the current, repository-relative working directory. + * + * @author Oliver Drotbohm + * @since 1.3 + */ +class WorkingDirectoryChangesDetector implements FileModificationDetector { + + private final FileModificationDetector delegate; + private final String workingDirectory; + + /** + * Creates a new {@link WorkingDirectoryChangesDetector} for the given {@link FileModificationDetector} delegate and + * directory; + * + * @param delegate must not be {@literal null}. + * @param workingDirectory must not be {@literal null}. + */ + WorkingDirectoryChangesDetector(FileModificationDetector delegate, String workingDirectory) { + + Assert.notNull(delegate, "FileModificationDetector must not be null!"); + Assert.notNull(workingDirectory, "Working folder must not be null!"); + + this.delegate = delegate; + this.workingDirectory = workingDirectory; + } + + /** + * Creates a new {@link WorkingDirectoryChangesDetector} for the current Git repository-relative working directory. + * + * @param delegate must not be {@literal null}. + * @return will never be {@literal null}. + */ + public static WorkingDirectoryChangesDetector of(FileModificationDetector delegate) { + + var pathToRepo = JGitUtil.withRepository(it -> it.getDirectory().getParent()); + + // someFolder/. + var currentWorkingDirectory = new File(".").getAbsolutePath(); + + // Strip repository base and /. + var repositoryRelative = currentWorkingDirectory.substring(pathToRepo.length() + 1, + currentWorkingDirectory.length() - 2); + + return new WorkingDirectoryChangesDetector(delegate, repositoryRelative); + } + + /* + * (non-Javadoc) + * @see org.springframework.modulith.junit.diff.FileModificationDetector#getModifiedFiles() + */ + @Override + public Stream getModifiedFiles() { + + return delegate.getModifiedFiles() + .filter(it -> it.path().startsWith(workingDirectory)) + .map(it -> it.asRelativeTo(workingDirectory)); + } +} diff --git a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/FileModificationDetectorUnitTests.java b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/FileModificationDetectorUnitTests.java index 8d8f3555..8427ead3 100644 --- a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/FileModificationDetectorUnitTests.java +++ b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/FileModificationDetectorUnitTests.java @@ -56,9 +56,9 @@ class FileModificationDetectorUnitTests { @Test // GH-31 void selectingDefaultExplicitlyUsesDefault() { - var explicitDetector = FileModificationDetector.getDetector(setupEnvironment("default", null)); + var explicitDetector = FileModificationDetector.getTargetDetector(setupEnvironment("default", null)); - assertThat(FileModificationDetector.getDetector(setupEnvironment(null, null))) + assertThat(FileModificationDetector.getTargetDetector(setupEnvironment(null, null))) .isEqualTo(explicitDetector); } @@ -74,7 +74,7 @@ class FileModificationDetectorUnitTests { var environment = setupEnvironment(detector, referenceCommit); - assertThat(FileModificationDetector.getDetector(environment)).isInstanceOf(expected); + assertThat(FileModificationDetector.getTargetDetector(environment)).isInstanceOf(expected); } private static Environment setupEnvironment(String detector, String referenceCommit) { diff --git a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ModifiedFileUnitTest.java b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ModifiedFileUnitTest.java new file mode 100644 index 00000000..f4e46783 --- /dev/null +++ b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ModifiedFileUnitTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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 + * + * https://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 org.springframework.modulith.junit.diff; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link ModifiedFile}. + * + * @author Oliver Drotbohm + * @since 1.3 + */ +class ModifiedFileUnitTest { + + @Test // GH-861 + void returnsRelativeFile() { + + assertThat(new ModifiedFile("foo/bar.txt").asRelativeTo("foo")) + .isEqualTo(new ModifiedFile("bar.txt")); + } + + @Test // GH-861 + void returnsFileAsIsForEmptyReference() { + + assertThat(new ModifiedFile("foo/bar.txt").asRelativeTo("")) + .isEqualTo(new ModifiedFile("foo/bar.txt")); + } +} diff --git a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetectorUnitTests.java b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetectorUnitTests.java new file mode 100644 index 00000000..19de96e7 --- /dev/null +++ b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/WorkingDirectoryChangesDetectorUnitTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024 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 + * + * https://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 org.springframework.modulith.junit.diff; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link WorkingDirectoryChangesDetector}. + * + * @author Oliver Drotbohm + * @since 1.3 + */ +@ExtendWith(MockitoExtension.class) +class WorkingDirectoryChangesDetectorUnitTests { + + @Mock FileModificationDetector delegate; + + @Test // GH-861 + void filtersFilesContainedInReferenceFolder() { + + when(delegate.getModifiedFiles()) + .thenReturn(Stream.of("rootPom.xml", "nested/nestedPom.xml").map(ModifiedFile::new)); + + var detector = new WorkingDirectoryChangesDetector(delegate, "nested"); + + assertThat(detector.getModifiedFiles()) + .extracting(ModifiedFile::path) + .containsExactly("nestedPom.xml"); + } +}