Skip to content

Commit 0119c85

Browse files
ValentinBossiodrotbohm
authored andcommitted
GH-1438 - Support to skip all tests if no code changes are detected.
Original pull request: GH-1484. Signed-off-by: Valentin Bossi <[email protected]>
1 parent 2fe724c commit 0119c85

File tree

8 files changed

+103
-9
lines changed

8 files changed

+103
-9
lines changed

spring-modulith-integration-test/src/test/java/org/springframework/modulith/junit/TestExecutionConditionUnitTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.junit.jupiter.api.Test;
2424
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
25+
import org.springframework.modulith.junit.Changes.OnNoChange;
2526
import org.springframework.modulith.junit.TestExecutionCondition.ConditionContext;
2627
import org.springframework.modulith.junit.diff.ModifiedFile;
2728

@@ -89,7 +90,7 @@ private void assertEnabled(Class<?> type, boolean expected, String... files) {
8990

9091
private void assertEnabled(Class<?> type, boolean expected, Stream<ModifiedFile> files) {
9192

92-
assertThat(condition.evaluate(new ConditionContext(type, Changes.of(files))))
93+
assertThat(condition.evaluate(new ConditionContext(type, Changes.of(files, OnNoChange.DEFAULT))))
9394
.extracting(ConditionEvaluationResult::isDisabled)
9495
.isNotEqualTo(expected);
9596
}

spring-modulith-junit/src/main/java/org/springframework/modulith/junit/Changes.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.modulith.junit;
1717

1818
import static java.util.stream.Collectors.*;
19+
import static org.springframework.modulith.junit.Changes.OnNoChange.EXECUTE_NO_TESTS;
1920

2021
import java.util.Collection;
2122
import java.util.Collections;
@@ -42,20 +43,23 @@
4243
*/
4344
public class Changes implements Iterable<Change> {
4445

45-
public static final Changes NONE = new Changes(Collections.emptySet());
46+
public static final Changes NONE = new Changes(Collections.emptySet(), OnNoChange.DEFAULT);
4647

4748
private final Collection<Change> changes;
49+
private final OnNoChange onNoChangeConfig;
4850

4951
/**
5052
* Creates a new {@link Changes} instance from the given {@link Change}s.
5153
*
5254
* @param changes must not be {@literal null}.
5355
*/
54-
private Changes(Collection<Change> changes) {
56+
private Changes(Collection<Change> changes, OnNoChange config) {
5557

5658
Assert.notNull(changes, "Changes must not be null!");
59+
Assert.notNull(config, "OnNoChange must not be null!");
5760

5861
this.changes = changes;
62+
this.onNoChangeConfig = config;
5963
}
6064

6165
/**
@@ -64,11 +68,11 @@ private Changes(Collection<Change> changes) {
6468
* @param files must not be {@literal null}.
6569
* @return will never be {@literal null}.
6670
*/
67-
static Changes of(Stream<ModifiedFile> files) {
71+
static Changes of(Stream<ModifiedFile> files, OnNoChange config) {
6872

6973
Assert.notNull(files, "Modified files must not be null!");
7074

71-
return files.map(Change::of).collect(collectingAndThen(toSet(), Changes::new));
75+
return files.map(Change::of).collect(collectingAndThen(toSet(), changes -> new Changes(changes, config)));
7276
}
7377

7478
/**
@@ -88,6 +92,10 @@ boolean hasClassChanges() {
8892
return !getChangedClasses().isEmpty();
8993
}
9094

95+
boolean skipTestsOnNoChanges() {
96+
return onNoChangeConfig == EXECUTE_NO_TESTS;
97+
}
98+
9199
boolean contains(Class<?> type) {
92100
return changes.stream().anyMatch(it -> it.hasOrigin(type.getName()));
93101
}
@@ -328,4 +336,20 @@ public final String toString() {
328336
}
329337
}
330338
}
339+
340+
enum OnNoChange {
341+
EXECUTE_ALL_TESTS("execute-all"),
342+
EXECUTE_NO_TESTS("execute-none");
343+
344+
final String value;
345+
static final OnNoChange DEFAULT = OnNoChange.EXECUTE_ALL_TESTS;
346+
347+
OnNoChange(String value) {
348+
this.value = value;
349+
}
350+
351+
public static OnNoChange propertyConfig(String value) {
352+
return EXECUTE_NO_TESTS.value.equals(value) ? EXECUTE_NO_TESTS : DEFAULT;
353+
}
354+
}
331355
}

spring-modulith-junit/src/main/java/org/springframework/modulith/junit/ChangesFactory.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.slf4j.Logger;
1919
import org.slf4j.LoggerFactory;
2020
import org.springframework.core.env.Environment;
21+
import org.springframework.modulith.junit.Changes.OnNoChange;
2122
import org.springframework.modulith.junit.diff.FileModificationDetector;
2223
import org.springframework.util.Assert;
2324

@@ -45,9 +46,11 @@ static Changes getChanges(Environment environment) {
4546
return Changes.NONE;
4647
}
4748

49+
var onNoChangesConfig = OnNoChange.propertyConfig(environment.getProperty("spring.modulith.test.on-no-changes"));
50+
4851
// Determine detector
4952
var detector = FileModificationDetector.getDetector(environment);
50-
var result = Changes.of(detector.getModifiedFiles());
53+
var result = Changes.of(detector.getModifiedFiles(), onNoChangesConfig);
5154

5255
if (log.isInfoEnabled()) {
5356

spring-modulith-junit/src/main/java/org/springframework/modulith/junit/TestExecutionCondition.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ ConditionEvaluationResult evaluate(ConditionContext context) {
5959
}
6060

6161
if (!changes.hasClassChanges()) {
62-
return enabled("No source file changes detected.");
62+
return changes.skipTestsOnNoChanges()
63+
? disabled("No source file changes detected — tests skipped due to configuration \"on-no-changes=execute-none\".")
64+
: enabled("No source file changes detected — running full test suite due to default configuration.");
6365
}
6466

6567
var changedClasses = changes.getChangedClasses();

spring-modulith-junit/src/main/resources/META-INF/spring-configuration-metadata.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,27 @@
1515
"name": "spring.modulith.test.reference-commit",
1616
"type": "java.lang.String",
1717
"description": "The hash of the commit to track back changes to. Usually set in a CI environment."
18+
},
19+
{
20+
"name": "spring.modulith.test.on-no-changes",
21+
"type": "java.lang.String",
22+
"description": "Whether all tests or no tests should be executed when no class changes are detected."
1823
}
1924
],
2025
"hints": [
26+
{
27+
"name": "spring.modulith.test.on-no-changes",
28+
"values": [
29+
{
30+
"value": "execute-all",
31+
"description" : "Executes all tests."
32+
},
33+
{
34+
"value": "execute-none",
35+
"description" : "Executes no tests."
36+
}
37+
]
38+
},
2139
{
2240
"name": "spring.modulith.test.file-modification-detector",
2341
"values": [

spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangesFactoryUnitTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.core.env.Environment;
2424
import org.springframework.core.env.MapPropertySource;
2525
import org.springframework.core.env.StandardEnvironment;
26+
import org.springframework.modulith.junit.Changes.OnNoChange;
2627

2728
/**
2829
* Unit tests for {@link ChangesFactory}.
@@ -37,6 +38,39 @@ void returnsNoChangesIfDisabledByProperty() {
3738
var environment = getEnvironment(Map.of("spring.modulith.test.skip-optimizations", "true"));
3839

3940
assertThat(ChangesFactory.getChanges(environment)).isEqualTo(Changes.NONE);
41+
assertThat(ChangesFactory.getChanges(environment).skipTestsOnNoChanges()).isFalse();
42+
}
43+
44+
@Test // GH-1438
45+
void skipTestsOnNoChangesSetByProperty() {
46+
47+
var environment = getEnvironment(Map.of("spring.modulith.test.on-no-changes", OnNoChange.EXECUTE_NO_TESTS.value));
48+
49+
assertThat(ChangesFactory.getChanges(environment).hasClassChanges()).isFalse();
50+
assertThat(ChangesFactory.getChanges(environment).skipTestsOnNoChanges()).isTrue();
51+
}
52+
53+
@Test // GH-1438
54+
void executeTestsOnNoChangesSetByProperty() {
55+
56+
var environment = getEnvironment(Map.of("spring.modulith.test.on-no-changes", OnNoChange.DEFAULT.value));
57+
58+
assertThat(ChangesFactory.getChanges(environment).hasClassChanges()).isFalse();
59+
assertThat(ChangesFactory.getChanges(environment).skipTestsOnNoChanges()).isFalse();
60+
}
61+
62+
@Test // GH-1438
63+
void executeTestsByDefaultOrInvalidValue() {
64+
65+
var environment = getEnvironment(Map.of("spring.modulith.test.on-no-changes", "1"));
66+
67+
assertThat(ChangesFactory.getChanges(environment).hasClassChanges()).isFalse();
68+
assertThat(ChangesFactory.getChanges(environment).skipTestsOnNoChanges()).isFalse();
69+
70+
environment = getEnvironment(Map.of());
71+
72+
assertThat(ChangesFactory.getChanges(environment).hasClassChanges()).isFalse();
73+
assertThat(ChangesFactory.getChanges(environment).skipTestsOnNoChanges()).isFalse();
4074
}
4175

4276
private Environment getEnvironment(Map<String, Object> properties) {

spring-modulith-junit/src/test/java/org/springframework/modulith/junit/ChangesUnitTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import org.junit.jupiter.api.DynamicTest;
2323
import org.junit.jupiter.api.Test;
2424
import org.junit.jupiter.api.TestFactory;
25+
import org.springframework.core.env.StandardEnvironment;
2526
import org.springframework.modulith.junit.Changes.Change.JavaSourceChange;
2627
import org.springframework.modulith.junit.Changes.Change.JavaTestSourceChange;
2728
import org.springframework.modulith.junit.Changes.Change.KotlinSourceChange;
2829
import org.springframework.modulith.junit.Changes.Change.KotlinTestSourceChange;
2930
import org.springframework.modulith.junit.Changes.Change.OtherFileChange;
31+
import org.springframework.modulith.junit.Changes.OnNoChange;
3032
import org.springframework.modulith.junit.diff.ModifiedFile;
3133

3234
/**
@@ -81,7 +83,7 @@ void shouldInterpredModifiedFilePathsCorrectly() {
8183
.map(ModifiedFile::new);
8284

8385
// when
84-
var result = Changes.of(modifiedFilePaths);
86+
var result = Changes.of(modifiedFilePaths, OnNoChange.DEFAULT);
8587

8688
// then
8789
assertThat(result.hasClasspathResourceChange()).isTrue();

spring-modulith-junit/src/test/java/org/springframework/modulith/junit/TestExecutionConditionUnitTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.junit.jupiter.api.Test;
2323
import org.springframework.boot.SpringBootConfiguration;
24+
import org.springframework.modulith.junit.Changes.OnNoChange;
2425
import org.springframework.modulith.junit.diff.ModifiedFile;
2526

2627
/**
@@ -33,12 +34,21 @@ class TestExecutionConditionUnitTests {
3334
@Test // GH-1391
3435
void fallsBackToEnabledTestIfMultipleMainClassesFound() {
3536

36-
var changes = Changes.of(Stream.of(new ModifiedFile("Foo.java")));
37+
var changes = Changes.of(Stream.of(new ModifiedFile("Foo.java")), OnNoChange.DEFAULT);
3738
var ctx = new TestExecutionCondition.ConditionContext(getClass(), changes);
3839

3940
assertThat(new TestExecutionCondition().evaluate(ctx).isDisabled()).isFalse();
4041
}
4142

43+
@Test // GH-1438
44+
void disablesForNoClassChangesWithPropertyConfiguration() {
45+
46+
var changes = Changes.of(Stream.of( new ModifiedFile("README.md")), OnNoChange.propertyConfig(OnNoChange.EXECUTE_NO_TESTS.value));
47+
var ctx = new TestExecutionCondition.ConditionContext(getClass(), changes);
48+
49+
assertThat(new TestExecutionCondition().evaluate(ctx).isDisabled()).isTrue();
50+
}
51+
4252
@SpringBootConfiguration
4353
static class First {}
4454

0 commit comments

Comments
 (0)