Skip to content

Commit

Permalink
Merge pull request #104 from gradle/lorinc/cc-fix-for-td
Browse files Browse the repository at this point in the history
Make plugin compatible with Config Cache & Test Distribution
  • Loading branch information
Lőrinc Pap authored Jun 8, 2021
2 parents c9eb9c4 + 5d339c8 commit a3f9d22
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.util.Set;
import java.util.concurrent.Callable;

import static org.gradle.testretry.internal.config.TestTaskConfigurer.supportsPropertyConventions;

public final class TestRetryTaskExtensionAdapter {

// for testing only
Expand Down Expand Up @@ -53,10 +55,6 @@ public TestRetryTaskExtensionAdapter(
initialize(extension, this.useConventions);
}

private static boolean supportsPropertyConventions(VersionNumber gradleVersion) {
return gradleVersion.compareTo(VersionNumber.parse("5.1")) >= 0;
}

private static void initialize(TestRetryTaskExtension extension, boolean gradle51OrLater) {
if (gradle51OrLater) {
extension.getMaxRetries().convention(DEFAULT_MAX_RETRIES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.gradle.api.internal.tasks.testing.JvmTestExecutionSpec;
import org.gradle.api.internal.tasks.testing.TestExecuter;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.testing.AbstractTestTask;
import org.gradle.api.tasks.testing.Test;
Expand All @@ -46,9 +48,53 @@ public static void configureTestTask(Test test, ObjectFactory objectFactory, Pro

test.getInputs().property("retry.failOnPassedAfterRetry", adapter.getFailOnPassedAfterRetryInput());

Provider<Boolean> isDeactivatedByTestDistributionPlugin =
shouldTestRetryPluginBeDeactivated(test, objectFactory, providerFactory, gradleVersion);
test.getInputs().property("isDeactivatedByTestDistributionPlugin", isDeactivatedByTestDistributionPlugin);

test.getExtensions().add(TestRetryTaskExtension.class, TestRetryTaskExtension.NAME, extension);
test.doFirst(new ConditionalTaskAction(new InitTaskAction(adapter, objectFactory)));
test.doLast(new ConditionalTaskAction(new FinalizeTaskAction()));

test.doFirst(new ConditionalTaskAction(isDeactivatedByTestDistributionPlugin, new InitTaskAction(adapter, objectFactory)));
test.doLast(new ConditionalTaskAction(isDeactivatedByTestDistributionPlugin, new FinalizeTaskAction()));
}

private static Provider<Boolean> shouldTestRetryPluginBeDeactivated(
Test test,
ObjectFactory objectFactory,
ProviderFactory providerFactory,
VersionNumber gradleVersion
) {
Provider<Boolean> result = providerFactory.provider(() -> callShouldTestRetryPluginBeDeactivated(test));
if (supportsPropertyConventions(gradleVersion)) {
Property<Boolean> property = objectFactory.property(Boolean.class).convention(result);
if (supportsFinalizeValueOnRead(gradleVersion)) {
property.finalizeValueOnRead();
}
result = property;
}
return result;
}

public static boolean supportsPropertyConventions(VersionNumber gradleVersion) {
return gradleVersion.compareTo(VersionNumber.parse("5.1")) >= 0;
}

private static boolean supportsFinalizeValueOnRead(VersionNumber gradleVersion) {
return gradleVersion.compareTo(VersionNumber.parse("6.1")) >= 0;
}

private static boolean callShouldTestRetryPluginBeDeactivated(Test test) {
Object distributionExtension = test.getExtensions().findByName("distribution");
if (distributionExtension == null) {
return false;
}
try {
Method result = makeAccessible(distributionExtension.getClass().getMethod("shouldTestRetryPluginBeDeactivated"));
return invoke(result, distributionExtension);
} catch (Exception e) {
test.getLogger().warn("Failed to determine whether test-retry plugin should be deactivated from distribution extension", e);
return false;
}
}

private static RetryTestExecuter createRetryTestExecuter(Test task, TestRetryTaskExtensionAdapter extension, ObjectFactory objectFactory) {
Expand All @@ -67,33 +113,22 @@ private static void setTestExecuter(Test task, RetryTestExecuter retryTestExecut

private static class ConditionalTaskAction implements Action<Task> {

private final Provider<Boolean> isDeactivatedByTestDistributionPlugin;
private final Action<Test> delegate;

public ConditionalTaskAction(Action<Test> delegate) {
public ConditionalTaskAction(Provider<Boolean> isDeactivatedByTestDistributionPlugin, Action<Test> delegate) {
this.isDeactivatedByTestDistributionPlugin = isDeactivatedByTestDistributionPlugin;
this.delegate = delegate;
}

@Override
public void execute(@NotNull Task task) {
if (isDeactivatedByTestDistributionPlugin(task)) {
if (isDeactivatedByTestDistributionPlugin.get()) {
task.getLogger().info("Test execution via the test-retry plugin is deactivated. Retries are handled by the test-distribution plugin.");
} else {
delegate.execute((Test) task);
}
}

private boolean isDeactivatedByTestDistributionPlugin(Task task) {
Object distributionExtension = task.getExtensions().findByName("distribution");
if (distributionExtension == null) {
return false;
}
try {
return invoke(method(distributionExtension.getClass(), "shouldTestRetryPluginBeDeactivated"), distributionExtension);
} catch (Exception e) {
task.getLogger().warn("Failed to determine whether test-retry plugin should be deactivated from distribution extension", e);
return false;
}
}
}

private static class FinalizeTaskAction implements Action<Test> {
Expand Down Expand Up @@ -134,14 +169,6 @@ private static Method declaredMethod(Class<?> type, String methodName, Class<?>.
}
}

private static Method method(Class<?> type, String methodName, Class<?>... paramTypes) {
try {
return makeAccessible(type.getMethod(methodName, paramTypes));
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

private static Method makeAccessible(Method method) {
method.setAccessible(true);
return method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

import java.util.stream.Collectors;

import static org.gradle.testretry.internal.executer.framework.TestFrameworkStrategy.gradleVersionIsAtLeast;

public final class RetryTestExecuter implements TestExecuter<JvmTestExecutionSpec> {

private final TestRetryTaskExtensionAdapter extension;
Expand Down Expand Up @@ -121,7 +123,7 @@ public void failWithNonRetriedTestsIfAny() {
}

private JvmTestExecutionSpec createRetryJvmExecutionSpec(JvmTestExecutionSpec spec, TestFramework retryTestFramework) {
if (TestFrameworkStrategy.gradleVersionIsAtLeast("6.4")) {
if (gradleVersionIsAtLeast("6.4")) {
// This constructor is in Gradle 6.4+
return new JvmTestExecutionSpec(
retryTestFramework,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import java.util.Map;
import java.util.Optional;

import static org.gradle.testretry.internal.executer.framework.TestFrameworkStrategy.gradleVersionIsAtLeast;

final class TestNgTestFrameworkStrategy implements TestFrameworkStrategy {

private static final Logger LOGGER = LoggerFactory.getLogger(TestNgTestFrameworkStrategy.class);
Expand Down Expand Up @@ -73,7 +75,7 @@ public TestFramework createRetrying(TestFrameworkTemplate template, TestNames fa
}

private TestNGTestFramework createTestFramework(TestFrameworkTemplate template, DefaultTestFilter retriedTestFilter) {
if (TestFrameworkStrategy.gradleVersionIsAtLeast("6.6")) {
if (gradleVersionIsAtLeast("6.6")) {
return new TestNGTestFramework(template.task, template.task.getClasspath(), retriedTestFilter, template.objectFactory);
} else {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ConfigCachingPluginFuncTest extends AbstractGeneralPluginFuncTest {
result = gradleRunnerWithConfigurationCache(gradleVersion).build()
then:
assertConfigurationCacheIsReused(result, gradleVersion)
configurationCacheIsReused(result, gradleVersion)
where:
gradleVersion << GRADLE_VERSIONS_UNDER_TEST
Expand All @@ -71,7 +71,39 @@ class ConfigCachingPluginFuncTest extends AbstractGeneralPluginFuncTest {
result = gradleRunnerWithConfigurationCache(gradleVersion).build()
then:
assertConfigurationCacheIsReused(result, gradleVersion)
configurationCacheIsReused(result, gradleVersion)
where:
gradleVersion << GRADLE_VERSIONS_UNDER_TEST
}
def "compatible with configuration cache when Test Distribution is also present (gradle version #gradleVersion)"() {
shouldTestConfigCache(gradleVersion)
buildFile << """
interface TestDistributionExtension {}
class DefaultTestDistributionExtension implements TestDistributionExtension {
boolean shouldTestRetryPluginBeDeactivated() {
true
}
}
test.extensions.create(TestDistributionExtension, "distribution", DefaultTestDistributionExtension)
"""
failedTest()
when:
def result = gradleRunnerWithConfigurationCache(gradleVersion, 'test', '--info').buildAndFail()
then:
!configurationCacheIsReused(result, gradleVersion)
result.output.contains('handled by the test-distribution plugin')
when:
result = gradleRunnerWithConfigurationCache(gradleVersion, 'test', '--info').buildAndFail()
then:
configurationCacheIsReused(result, gradleVersion)
result.output.contains('handled by the test-distribution plugin')
where:
gradleVersion << GRADLE_VERSIONS_UNDER_TEST
Expand All @@ -86,8 +118,8 @@ class ConfigCachingPluginFuncTest extends AbstractGeneralPluginFuncTest {
GradleVersion.version(gradleVersion) >= GradleVersion.version("6.1")
}
static void assertConfigurationCacheIsReused(BuildResult result, String gradleVersion) {
assert result.output.contains(getConfigurationCacheMessage(gradleVersion))
private static boolean configurationCacheIsReused(BuildResult result, String gradleVersion) {
result.output.contains(getConfigurationCacheMessage(gradleVersion))
}
static String[] withConfigurationCacheArguments(String gradleVersion, String[] arguments) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ class TestDistributionIntegrationFuncTest extends AbstractGeneralPluginFuncTest
buildFile << """
test.retry.maxRetries = 1
"""
failedTest()
}

def "is deactivated when decorated distribution extension returns true (gradle version #gradleVersion)"() {
given:
failedTest()
buildFile << """
interface TestDistributionExtension {}
class DefaultTestDistributionExtension implements TestDistributionExtension {
Expand All @@ -49,8 +49,43 @@ class TestDistributionIntegrationFuncTest extends AbstractGeneralPluginFuncTest
gradleVersion << GRADLE_VERSIONS_UNDER_TEST
}
def "is deactivated when decorated distribution extension changes to true (gradle version #gradleVersion)"() {
given:
successfulTest() // a failing one prohibit task outputs from being cached
buildFile << """
interface TestDistributionExtension {}
class DefaultTestDistributionExtension implements TestDistributionExtension {
boolean shouldTestRetryPluginBeDeactivated() {
Boolean.getBoolean("shouldTestRetryPluginBeDeactivated")
}
}
test.extensions.create(TestDistributionExtension.class, "distribution", DefaultTestDistributionExtension.class)
"""
when:
System.setProperty("shouldTestRetryPluginBeDeactivated", "${true}")
def result = gradleRunner(gradleVersion, 'test', '--info').build()
then:
result.output.contains("handled by the test-distribution plugin")
when:
System.setProperty("shouldTestRetryPluginBeDeactivated", "${false}")
result = gradleRunner(gradleVersion, 'test', '--info').build()
then:
with(result.output) {
!contains("handled by the test-distribution plugin")
!contains("> Task :test UP-TO-DATE")
}
where:
gradleVersion << GRADLE_VERSIONS_UNDER_TEST
}
def "is deactivated when undecorated distribution extension returns true (gradle version #gradleVersion)"() {
given:
failedTest()
buildFile << """
class TestDistributionExtension {
boolean shouldTestRetryPluginBeDeactivated() {
Expand All @@ -72,6 +107,7 @@ class TestDistributionIntegrationFuncTest extends AbstractGeneralPluginFuncTest
def "is not deactivated when distribution extension returns false (gradle version #gradleVersion)"() {
given:
failedTest()
buildFile << """
interface TestDistributionExtension {}
class DefaultTestDistributionExtension implements TestDistributionExtension {
Expand All @@ -94,6 +130,7 @@ class TestDistributionIntegrationFuncTest extends AbstractGeneralPluginFuncTest
def "is not deactivated when distribution extension does not declare the expected method (gradle version #gradleVersion)"() {
given:
failedTest()
buildFile << """
class TestDistributionExtension {
}
Expand Down

0 comments on commit a3f9d22

Please sign in to comment.