findUncommittedChanges(Repository repository) {
+
+ return withTry(() -> new Git(repository), git -> {
+
+ var status = git.status().call();
return Streams.concat(status.getUncommittedChanges().stream(), status.getUntracked().stream())
- .map(ModifiedFilePath::new);
- } catch (GitAPIException e) {
- throw new IOException("Unable to find uncommitted changes", e);
- }
+ .map(ModifiedFile::new);
+ });
}
-
}
diff --git a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/UnpushedCommitsDetector.java b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/UnpushedCommitsDetector.java
index 0995e9e53..3683786cc 100644
--- a/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/UnpushedCommitsDetector.java
+++ b/spring-modulith-junit/src/main/java/org/springframework/modulith/junit/diff/UnpushedCommitsDetector.java
@@ -1,15 +1,25 @@
+/*
+ * 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.IOException;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
+import static org.springframework.modulith.junit.diff.JGitUtil.*;
+
import java.util.stream.Stream;
-import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.BranchConfig;
-import org.springframework.core.env.PropertyResolver;
-import org.springframework.lang.NonNull;
/**
*
@@ -17,25 +27,30 @@
*
* To be precise, this finds the diff between the local HEAD and its tracking branch and the uncommitted and untracked
* changes. Note: This will not fetch from the remote first!
+ *
+ * @author Lukas Dohmen
+ * @author David Bilge
+ * @author Oliver Drotbohm
*/
-public class UnpushedCommitsDetector implements FileModificationDetector {
+enum UnpushedCommitsDetector implements FileModificationDetector {
+ INSTANCE;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.modulith.junit.FileModificationDetector#getModifiedFiles()
+ */
@Override
- public @NonNull Set getModifiedFiles(@NonNull PropertyResolver propertyResolver)
- throws IOException {
- try (var repo = JGitUtil.buildRepository()) {
- String localBranch = repo.getFullBranch();
- String trackingBranch = new BranchConfig(repo.getConfig(), repo.getBranch()).getTrackingBranch();
-
- Stream diff = localBranch != null && trackingBranch != null
- ? JGitUtil.diffRefs(repo, localBranch, trackingBranch)
- : Stream.empty();
+ public Stream getModifiedFiles() {
- HashSet result = new HashSet<>();
- result.addAll(new UncommittedChangesDetector().getModifiedFiles(propertyResolver));
- result.addAll(JGitUtil.convertDiffEntriesToFileChanges(diff).collect(Collectors.toSet()));
- return result;
- }
- }
+ return withRepository(repo -> {
+
+ var localBranch = repo.getFullBranch();
+ var trackingBranch = new BranchConfig(repo.getConfig(), repo.getBranch()).getTrackingBranch();
+ return localBranch != null && trackingBranch != null
+ ? toModifiedFiles(repo, localBranch, trackingBranch)
+ : Stream.empty();
+ });
+ }
}
diff --git a/spring-modulith-junit/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-modulith-junit/src/main/resources/META-INF/additional-spring-configuration-metadata.json
deleted file mode 100644
index d96288d63..000000000
--- a/spring-modulith-junit/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "groups": [
- {
- "name": "spring.modulith.test",
- "description": "Properties configuring the execution of modulith-specific tests."
- }
- ],
- "properties": [
- {
- "name": "spring.modulith.test.changed-files-strategy",
- "type": "java.lang.String",
- "defaultValue": "org.springframework.modulith.UncommittedChangesStrategy",
- "description": "A strategy to determine the list of changed files to consider for test execution. This should be a fully qualified class name of a registered implementation."
- }
- ],
- "hints": []
-}
diff --git a/spring-modulith-junit/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/spring-modulith-junit/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
index 924a6a1bd..917af696e 100644
--- a/spring-modulith-junit/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
+++ b/spring-modulith-junit/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -1 +1 @@
-org.springframework.modulith.junit.ModulithExecutionExtension
+org.springframework.modulith.junit.ModulithExecutionCondition
diff --git a/spring-modulith-junit/src/main/resources/META-INF/services/org.springframework.modulith.FileModificationDetector b/spring-modulith-junit/src/main/resources/META-INF/services/org.springframework.modulith.FileModificationDetector
deleted file mode 100644
index b96b44066..000000000
--- a/spring-modulith-junit/src/main/resources/META-INF/services/org.springframework.modulith.FileModificationDetector
+++ /dev/null
@@ -1,3 +0,0 @@
-org.springframework.modulith.git.UncommittedChangesDetector
-org.springframework.modulith.git.UnpushedGitChangesDetector
-org.springframework.modulith.git.DiffDetector
diff --git a/spring-modulith-junit/src/main/resources/META-INF/spring-configuration-metadata.json b/spring-modulith-junit/src/main/resources/META-INF/spring-configuration-metadata.json
new file mode 100644
index 000000000..154dec4c1
--- /dev/null
+++ b/spring-modulith-junit/src/main/resources/META-INF/spring-configuration-metadata.json
@@ -0,0 +1,47 @@
+{
+ "groups": [
+ {
+ "name": "spring.modulith.test",
+ "description": "Properties configuring the execution of modulith-specific tests."
+ }
+ ],
+ "properties": [
+ {
+ "name": "spring.modulith.test.file-modification-detector",
+ "type": "java.lang.String",
+ "description": "A strategy to determine the list of changed files to consider for test execution. The default will consider a potentially configured spring.modulith.test.reference-commit and fall back to both uncommited changes and the ones made in commits on top of the current tracking branch."
+ },
+ {
+ "name": "spring.modulith.test.reference-commit",
+ "type": "java.lang.String",
+ "description": "The hash of the commit to track back changes to. Usually set in a CI environment."
+ }
+ ],
+ "hints": [
+ {
+ "name": "spring.modulith.test.file-modification-detector",
+ "values": [
+ {
+ "value": "uncommitted-changes",
+ "description" : "Considers all uncommitted changes."
+ },
+ {
+ "value": "default",
+ "description" : "Consider both uncommited changes and the ones made in commits on top of the current tracking branch."
+ },
+ {
+ "value": "reference-commit",
+ "description" : "Considers changes between the current head and a given reference commit set in spring.modulith.test.reference-commit."
+ }
+ ],
+ "providers": [
+ {
+ "name": "class-reference",
+ "parameters": {
+ "target": "org.springframework.modulith.junit.diff.FileModificationDetector"
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/spring-modulith-junit/src/test/java/example/a/package-info.java b/spring-modulith-junit/src/test/java/example/a/package-info.java
new file mode 100644
index 000000000..be87a7644
--- /dev/null
+++ b/spring-modulith-junit/src/test/java/example/a/package-info.java
@@ -0,0 +1 @@
+package example.a;
diff --git a/spring-modulith-junit/src/test/java/example/b/package-info.java b/spring-modulith-junit/src/test/java/example/b/package-info.java
new file mode 100644
index 000000000..58d83ab5a
--- /dev/null
+++ b/spring-modulith-junit/src/test/java/example/b/package-info.java
@@ -0,0 +1 @@
+package example.b;
diff --git a/spring-modulith-junit/src/test/java/example/package-info.java b/spring-modulith-junit/src/test/java/example/package-info.java
new file mode 100644
index 000000000..e83ec38c2
--- /dev/null
+++ b/spring-modulith-junit/src/test/java/example/package-info.java
@@ -0,0 +1 @@
+package example;
diff --git a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangeTest.java b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangeTest.java
deleted file mode 100644
index 0531b6342..000000000
--- a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangeTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.springframework.modulith.junit;
-
-import static org.assertj.core.api.Assertions.*;
-
-import java.util.Set;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.modulith.junit.Change.JavaClassChange;
-import org.springframework.modulith.junit.Change.JavaTestClassChange;
-import org.springframework.modulith.junit.Change.OtherFileChange;
-import org.springframework.modulith.junit.diff.ModifiedFilePath;
-
-class ChangesTest {
- @Test
- void shouldInterpredModifiedFilePathsCorrectly() {
- // given
- Set modifiedFilePaths = Set.of(
- new ModifiedFilePath("spring-modulith-junit/src/main/java/org/springframework/modulith/Changes.java"),
- new ModifiedFilePath("spring-modulith-junit/src/test/java/org/springframework/modulith/ChangesTest.java"),
- new ModifiedFilePath(
- "spring-modulith-junit/src/main/resources/META-INF/additional-spring-configuration-metadata.json"));
-
- // when
- Set result = Changes.toChanges(modifiedFilePaths);
-
- // then
- assertThat(result).containsExactlyInAnyOrder(
- new JavaClassChange("org.springframework.modulith.Changes"),
- new JavaTestClassChange("org.springframework.modulith.ChangesTest"),
- new OtherFileChange(
- "spring-modulith-junit/src/main/resources/META-INF/additional-spring-configuration-metadata.json"));
- }
-}
diff --git a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangesUnitTests.java b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangesUnitTests.java
new file mode 100644
index 000000000..644f3833b
--- /dev/null
+++ b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangesUnitTests.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.springframework.modulith.junit.Changes.Change.JavaSourceChange;
+import org.springframework.modulith.junit.Changes.Change.JavaTestSourceChange;
+import org.springframework.modulith.junit.Changes.Change.OtherFileChange;
+import org.springframework.modulith.junit.diff.ModifiedFile;
+
+/**
+ * Unit tests for {@link Changes}.
+ *
+ * @author Lukas Dohmen
+ * @author David Bilge
+ * @author Oliver Drotbohm
+ */
+class ChangesUnitTests {
+
+ @TestFactory // GH-31
+ Stream detectsClasspathFileChange() {
+
+ var files = Stream.of("src/main/resources/some.txt", "src/test/resources/some.txt");
+
+ return DynamicTest.stream(files, it -> it + " is considered classpath resource", it -> {
+ assertThat(new OtherFileChange(it).isClasspathResource()).isTrue();
+ });
+ }
+
+ @TestFactory // GH-31
+ Stream detectsNonClasspathFileChange() {
+
+ var files = Stream.of("pom.xml", "build.gradle", "build.kt");
+
+ return DynamicTest.stream(files, it -> it + " is considered build resource", it -> {
+
+ var change = new OtherFileChange(it);
+
+ assertThat(change.isClasspathResource()).isFalse();
+ assertThat(change.affectsBuildResource()).isTrue();
+ });
+ }
+
+ @Test // GH-31
+ void shouldInterpredModifiedFilePathsCorrectly() {
+
+ // given
+ var modifiedFilePaths = Stream.of(
+ "src/main/java/org/springframework/modulith/junit/Changes.java",
+ "src/test/java/org/springframework/modulith/ChangesTest.java",
+ "src/main/resources/META-INF/additional-spring-configuration-metadata.json")
+ .map(ModifiedFile::new);
+
+ // when
+ var result = Changes.of(modifiedFilePaths);
+
+ // then
+ assertThat(result.hasClasspathResourceChange()).isTrue();
+ assertThat(result).containsExactlyInAnyOrder(
+ new JavaSourceChange("org.springframework.modulith.junit.Changes"),
+ new JavaTestSourceChange("org.springframework.modulith.ChangesTest"),
+ new OtherFileChange("src/main/resources/META-INF/additional-spring-configuration-metadata.json"));
+ }
+}
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
new file mode 100644
index 000000000..8d8f3555f
--- /dev/null
+++ b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/FileModificationDetectorUnitTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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 java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.core.env.Environment;
+import org.springframework.mock.env.MockEnvironment;
+
+/**
+ * Unit tests for {@link FileModificationDetector}.
+ *
+ * @author Oliver Drotbohm
+ */
+class FileModificationDetectorUnitTests {
+
+ @Test // GH-31
+ void usesReferenceCommitDetectionIfHashConfigured() {
+ assertDetector(ReferenceCommitDetector.class, null, "HEAD^");
+ }
+
+ @Test // GH-31
+ void usesReferenceCommitDetectionIfConfiguredExplicitly() {
+ assertDetector(ReferenceCommitDetector.class, "reference-commit", null);
+ }
+
+ @Test // GH-31
+ void usesUncommittedChangesIfConfiguredExplicitly() {
+ assertDetector(UncommittedChangesDetector.class, "uncommitted-changes", null);
+ }
+
+ @Test // GH-31
+ void registersCustomDetectorByType() {
+
+ var customDetector = CustomFileModificationDetector.class;
+
+ assertDetector(customDetector, customDetector.getName(), null);
+ }
+
+ @Test // GH-31
+ void selectingDefaultExplicitlyUsesDefault() {
+
+ var explicitDetector = FileModificationDetector.getDetector(setupEnvironment("default", null));
+
+ assertThat(FileModificationDetector.getDetector(setupEnvironment(null, null)))
+ .isEqualTo(explicitDetector);
+ }
+
+ @Test // GH-31
+ void rejectsInvalidDetectorName() {
+
+ assertThatIllegalStateException().isThrownBy(() -> {
+ FileModificationDetector.getDetector(setupEnvironment("some.Garbage", null));
+ });
+ }
+
+ private static void assertDetector(Class> expected, String detector, String referenceCommit) {
+
+ var environment = setupEnvironment(detector, referenceCommit);
+
+ assertThat(FileModificationDetector.getDetector(environment)).isInstanceOf(expected);
+ }
+
+ private static Environment setupEnvironment(String detector, String referenceCommit) {
+
+ var environment = new MockEnvironment();
+
+ if (detector != null) {
+ environment.setProperty("spring.modulith.test.file-modification-detector", detector);
+ }
+
+ if (referenceCommit != null) {
+ environment.setProperty("spring.modulith.test.reference-commit", referenceCommit);
+ }
+
+ return environment;
+ }
+
+ static class CustomFileModificationDetector implements FileModificationDetector {
+
+ @Override
+ public Stream getModifiedFiles() {
+ return Stream.empty();
+ }
+ }
+}
diff --git a/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ReferenceCommitDetectorUnitTests.java b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ReferenceCommitDetectorUnitTests.java
new file mode 100644
index 000000000..1c43a480c
--- /dev/null
+++ b/spring-modulith-junit/src/test/java/org/springframework/modulith/junit/diff/ReferenceCommitDetectorUnitTests.java
@@ -0,0 +1,36 @@
+/*
+ * 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 ReferenceCommitDetector}.
+ *
+ * @author Oliver Drotbohm
+ */
+class ReferenceCommitDetectorUnitTests {
+
+ @Test // GH-31
+ void detectsChangesOfHead() {
+
+ var detector = new ReferenceCommitDetector(null);
+
+ assertThat(detector.getModifiedFiles()).isEmpty();
+ }
+}