com.palantir.baseline.plugins.BaselineTesting Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-baseline-java Show documentation
Show all versions of gradle-baseline-java Show documentation
A Gradle plugin for applying Baseline-recommended build and IDE settings
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.baseline.plugins;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.palantir.baseline.tasks.CheckJUnitDependencies;
import com.palantir.baseline.util.VersionUtils;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.result.ResolvedComponentResult;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.testing.Test;
import org.gradle.api.tasks.testing.TestFrameworkOptions;
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
import org.gradle.api.tasks.testing.logging.TestLogEvent;
import org.gradle.util.GradleVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class BaselineTesting implements Plugin {
private static final Logger log = LoggerFactory.getLogger(BaselineTesting.class);
@Override
public void apply(Project project) {
project.getTasks().withType(Test.class).configureEach(task -> {
task.jvmArgs("-XX:+HeapDumpOnOutOfMemoryError", "-XX:+CrashOnOutOfMemoryError");
if (!Objects.equals("true", project.findProperty("com.palantir.baseline.restore-test-cache"))) {
// Never cache test tasks, until we work out the correct inputs for ETE / integration tests
task.getOutputs().cacheIf(t -> false);
}
// repos that use 'snapshot' style testing should all use one convenient task to refresh the snapshots,
// ./gradlew test -Drecreate=true
boolean shouldRecreate = Boolean.getBoolean("recreate");
task.systemProperty("recreate", Boolean.toString(shouldRecreate));
if (shouldRecreate) {
task.getOutputs().upToDateWhen(t -> false);
}
});
project.getPluginManager().withPlugin("java-base", unusedPlugin -> {
TaskProvider checkJUnitDependencies =
project.getTasks().register("checkJUnitDependencies", CheckJUnitDependencies.class);
project.getConvention()
.getPlugin(JavaPluginConvention.class)
.getSourceSets()
.configureEach(sourceSet -> {
getTestTaskForSourceSet(project, sourceSet).ifPresent(testTask -> {
testTask.dependsOn(checkJUnitDependencies);
});
ifHasResolvedCompileDependenciesMatching(
project,
sourceSet,
BaselineTesting::requiresJunitPlatform,
() -> fixSourceSet(project, sourceSet));
});
});
}
private void fixSourceSet(Project project, SourceSet ss) {
Optional maybeTestTask = getTestTaskForSourceSet(project, ss);
if (!maybeTestTask.isPresent()) {
log.warn("Detected 'org:junit.jupiter:junit-jupiter', but unable to find test task");
return;
}
log.info(
"Detected 'org:junit.jupiter:junit-jupiter', enabling useJUnitPlatform() on {}",
maybeTestTask.get().getName());
enableJunit5ForTestTask(maybeTestTask.get());
}
public static Optional getTestTaskForSourceSet(Project proj, SourceSet ss) {
String testTaskName = ss.getTaskName(null, "test");
Task task1 = proj.getTasks().findByName(testTaskName);
if (task1 instanceof Test) {
return Optional.of((Test) task1);
}
// unbroken dome does this
Task task2 = proj.getTasks().findByName(ss.getName());
if (task2 instanceof Test) {
return Optional.of((Test) task2);
}
return Optional.empty();
}
private static void ifHasResolvedCompileDependenciesMatching(
Project project, SourceSet sourceSet, Predicate spec, Runnable runnable) {
project.getConfigurations()
.getByName(sourceSet.getRuntimeClasspathConfigurationName())
.getIncoming()
.afterResolve(deps -> {
boolean anyMatch = deps.getResolutionResult().getAllComponents().stream()
.map(ResolvedComponentResult::getId)
.filter(ModuleComponentIdentifier.class::isInstance)
.map(ModuleComponentIdentifier.class::cast)
.anyMatch(spec);
if (anyMatch) {
runnable.run();
}
});
}
private static boolean requiresJunitPlatform(ModuleComponentIdentifier dep) {
return isDep(dep, "org.junit.jupiter", "junit-jupiter")
|| (isDep(dep, "org.spockframework", "spock-core")
&& VersionUtils.majorVersionNumber(dep.getVersion()) >= 2);
}
private static boolean isDep(ModuleComponentIdentifier dep, String group, String name) {
return group.equals(dep.getGroup()) && name.equals(dep.getModule());
}
private static void enableJunit5ForTestTask(Test task) {
if (!useJUnitPlatformEnabled(task)) {
task.useJUnitPlatform();
}
task.systemProperty("junit.platform.output.capture.stdout", "true");
task.systemProperty("junit.platform.output.capture.stderr", "true");
// https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution
task.systemProperty("junit.jupiter.execution.parallel.enabled", "true");
// Computes the desired parallelism based on the number of available processors/cores
task.systemProperty("junit.jupiter.execution.parallel.config.strategy", "dynamic");
// provide some stdout feedback when tests fail when running on CI and locally
task.getTestLogging().getEvents().add(TestLogEvent.FAILED);
// Only on CI and for non-unit test tasks, print out more detailed test information to avoid hitting the
// circleci 10 min deadline if there are lots of tests. Don't do this locally to avoid spamming massive
// amount of info for people running tests through the command line. Only for non unit test tasks as unit
// test tasks tend to be fast and avoid this issue.
if (!task.getName().equals("test") && "true".equals(System.getenv("CI"))) {
task.getTestLogging()
.getEvents()
.addAll(ImmutableSet.of(TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.SKIPPED));
}
}
@SuppressWarnings("IllegalImports")
public static boolean useJUnitPlatformEnabled(Test task) {
// Starting with Gradle 7.3, the test framework property is getting finalized and can't be set multiple times.
// Using getter such as 'Test#getOptions' will set a default test framework if it is not configured yet and
// finalize this value. This result in errors when trying to set the test framework at a later point.
// For Gradle 7.3, we can actually access the test framework as a property without finalizing its value which
// allows us to check if it's already configured.
// See: https: // github.com/palantir/gradle-baseline/pull/1974
if (GradleVersion.current().compareTo(GradleVersion.version("7.3")) < 0) {
return task.getOptions() instanceof JUnitPlatformOptions;
}
return getTestFrameworkWithReflection(task)
.map(org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestFramework.class::isInstance)
.getOrElse(false);
}
@SuppressWarnings("IllegalImports")
public static Set getJUnitPlatformEngines(Test task) {
// Starting with Gradle 7.3, the test framework property is getting finalized and can't be set multiple times.
// Using getter such as 'Test#getOptions' will set a default test framework if it is not configured yet and
// finalize this value. This result in errors when trying to set the test framework at a later point.
// For Gradle 7.3, we can actually access the test framework as a property without finalizing its value which
// allows us to check if it's already configured.
// See: https: // github.com/palantir/gradle-baseline/pull/1974
if (GradleVersion.current().compareTo(GradleVersion.version("7.3")) < 0) {
TestFrameworkOptions testOpts = task.getOptions();
if (testOpts instanceof JUnitPlatformOptions) {
JUnitPlatformOptions platformOptions = (JUnitPlatformOptions) testOpts;
return ImmutableSet.copyOf(platformOptions.getIncludeEngines());
} else {
return ImmutableSet.of();
}
}
return getTestFrameworkWithReflection(task)
.map(framework -> {
if (framework
instanceof org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestFramework) {
org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestFramework
platformFramework =
(org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestFramework)
framework;
return ImmutableSet.copyOf(
platformFramework.getOptions().getIncludeEngines());
}
return ImmutableSet.of();
})
.getOrElse(ImmutableSet.of());
}
@SuppressWarnings("IllegalImports")
private static Property getTestFrameworkWithReflection(
Test task) {
try {
Method getTestFrameworkProperty = Test.class.getMethod("getTestFrameworkProperty");
return (Property)
getTestFrameworkProperty.invoke(task);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(
String.format(
"Error calling Test#getTestFrameworkProperty reflectively on Gradle version %s",
GradleVersion.current()),
e);
}
}
}