diff --git a/changelog/@unreleased/pr-951.v2.yml b/changelog/@unreleased/pr-951.v2.yml new file mode 100644 index 000000000..523008ba7 --- /dev/null +++ b/changelog/@unreleased/pr-951.v2.yml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: checkJUnitDependencies detects a possible misconfiguration with spock + and JUnit5 which could lead to tests silently not running. + links: + - https://github.com/palantir/gradle-baseline/pull/951 diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckJUnitDependencies.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckJUnitDependencies.java index 246444e4c..e357250d2 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckJUnitDependencies.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckJUnitDependencies.java @@ -22,10 +22,12 @@ import java.io.IOException; import java.nio.file.Files; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; import org.gradle.api.DefaultTask; import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; @@ -63,8 +65,16 @@ public final void validateDependencies() { } private void validateSourceSet(SourceSet ss, Test task) { - boolean junitJupiterIsPresent = hasDep( - ss.getRuntimeClasspathConfigurationName(), CheckJUnitDependencies::isJunitJupiter); + Set deps = getProject().getConfigurations() + .getByName(ss.getRuntimeClasspathConfigurationName()) + .getIncoming() + .getResolutionResult() + .getAllComponents(); + boolean junitJupiterIsPresent = hasDep(deps, CheckJUnitDependencies::isJunitJupiter); + boolean vintageEngineExists = hasDep(deps, CheckJUnitDependencies::isVintageEngine); + boolean spockDependency = hasDep(deps, CheckJUnitDependencies::isSpock); + String testRuntimeOnly = ss.getRuntimeOnlyConfigurationName(); + boolean junitPlatformEnabled = BaselineTesting.useJUnitPlatformEnabled(task); // If some testing library happens to provide the junit-jupiter-api, then users might start using the // org.junit.jupiter.api.Test annotation, but as JUnit4 knows nothing about these, they'll silently not run @@ -72,7 +82,7 @@ private void validateSourceSet(SourceSet ss, Test task) { if (sourceSetMentionsJUnit5Api(ss)) { String implementation = ss.getImplementationConfigurationName(); Preconditions.checkState( - BaselineTesting.useJUnitPlatformEnabled(task), + junitPlatformEnabled, "Some tests mention JUnit5, but the '" + task.getName() + "' task does not have " + "useJUnitPlatform() enabled. This means tests may be silently not running! Please " + "add the following:\n\n" @@ -83,8 +93,7 @@ private void validateSourceSet(SourceSet ss, Test task) { // same time. It's crucial that they have the vintage engine set up correctly, otherwise tests may silently // not run! if (sourceSetMentionsJUnit4(ss)) { - if (BaselineTesting.useJUnitPlatformEnabled(task)) { // people might manually enable this - String testRuntimeOnly = ss.getRuntimeConfigurationName() + "Only"; + if (junitPlatformEnabled) { // people might manually enable this Preconditions.checkState( junitJupiterIsPresent, "Tests may be silently not running! Some tests still use JUnit4, but Gradle has " @@ -93,8 +102,6 @@ private void validateSourceSet(SourceSet ss, Test task) { + " " + testRuntimeOnly + " 'org.junit.jupiter:junit-jupiter'\n\n" + "Otherwise they will silently not run."); - boolean vintageEngineExists = hasDep( - ss.getRuntimeClasspathConfigurationName(), CheckJUnitDependencies::isVintageEngine); Preconditions.checkState( vintageEngineExists, "Tests may be silently not running! Some tests still use JUnit4, but Gradle has " @@ -109,28 +116,20 @@ private void validateSourceSet(SourceSet ss, Test task) { + "'org.junit.jupiter:junit-jupiter' dependency " + "because tests use JUnit4 and useJUnitPlatform() is not enabled."); } - } else { - String compileClasspath = ss.getCompileClasspathConfigurationName(); - boolean compilingAgainstOldJunit = hasDep(compileClasspath, CheckJUnitDependencies::isJunit4); - if (compilingAgainstOldJunit) { - getProject().getLogger().info( - "Extraneous dependency on JUnit4 (no test mentions JUnit4 classes). Please exclude " - + "this from compilation to ensure developers don't accidentally re-introduce it, " - + "e.g.\n\n configurations." + compileClasspath + ".exclude module: 'junit'\n\n"); - } } - // sourcesets might also contain Spock classes, but we don't have any special validation for these. + // spock uses JUnit4 under the hood, so the vintage engine is critical + if (spockDependency && junitPlatformEnabled) { + Preconditions.checkState( + vintageEngineExists, + "Tests may be silently not running! Spock dependency detected (which uses " + + "a JUnit4 Runner under the hood). Please add the following:\n\n" + + " " + testRuntimeOnly + " 'org.junit.vintage:junit-vintage-engine'\n\n"); + } } - private boolean hasDep(String configurationName, Predicate spec) { - return getProject().getConfigurations() - .getByName(configurationName) - .getIncoming() - .getResolutionResult() - .getAllComponents() - .stream() - .anyMatch(component -> spec.test(component.getModuleVersion())); + private boolean hasDep(Set deps, Predicate spec) { + return deps.stream().anyMatch(component -> spec.test(component.getModuleVersion())); } private boolean sourceSetMentionsJUnit4(SourceSet ss) { @@ -166,7 +165,7 @@ private static boolean isVintageEngine(ModuleVersionIdentifier dep) { return "org.junit.vintage".equals(dep.getGroup()) && "junit-vintage-engine".equals(dep.getName()); } - private static boolean isJunit4(ModuleVersionIdentifier dep) { - return "junit".equals(dep.getGroup()) && "junit".equals(dep.getName()); + private static boolean isSpock(ModuleVersionIdentifier dep) { + return "org.spockframework".equals(dep.getGroup()) && "spock-core".equals(dep.getName()); } } diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineTestingIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineTestingIntegrationTest.groovy index 5e8062137..56698e7a7 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineTestingIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineTestingIntegrationTest.groovy @@ -144,4 +144,20 @@ class BaselineTestingIntegrationTest extends AbstractPluginTest { BuildResult result = with('checkJUnitDependencies').buildAndFail() result.output.contains 'Some tests mention JUnit5, but the \'test\' task does not have useJUnitPlatform() enabled' } + + def 'checkJUnitDependencies ensures nebula test => vintage must be present'() { + when: + buildFile << standardBuildFile + buildFile << ''' + apply plugin: 'groovy' + dependencies { + testImplementation "org.junit.jupiter:junit-jupiter:5.4.2" + testImplementation 'com.netflix.nebula:nebula-test:7.3.0' + } + '''.stripIndent() + + then: + BuildResult result = with('checkJUnitDependencies').buildAndFail() + result.output.contains 'Tests may be silently not running! Spock dependency detected' + } }