Skip to content

Commit

Permalink
Allow specifying which classes may be retried (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldaley authored Nov 24, 2020
1 parent 9828370 commit 4746bee
Show file tree
Hide file tree
Showing 30 changed files with 1,026 additions and 63 deletions.
108 changes: 107 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ The `retry` extension is of the following type:
----
package org.gradle.testretry;
import org.gradle.api.Action;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.testing.Test;
/**
Expand All @@ -67,6 +69,11 @@ import org.gradle.api.tasks.testing.Test;
*/
public interface TestRetryTaskExtension {
/**
* The name of the extension added to each test task.
*/
String NAME = "retry";
/**
* Whether tests that initially fail and then pass on retry should fail the task.
* <p>
Expand All @@ -75,7 +82,7 @@ public interface TestRetryTaskExtension {
* <p>
* This setting has no effect if {@link Test#getIgnoreFailures()} is set to true.
*
* @return whether tests that initially fail and then pass on retry should fail the task
* @return whether tests that initially fails and then pass on retry should fail the task
*/
Property<Boolean> getFailOnPassedAfterRetry();
Expand Down Expand Up @@ -104,6 +111,77 @@ public interface TestRetryTaskExtension {
*/
Property<Integer> getMaxFailures();
/**
* The filter for specifying which tests may be retried.
*/
Filter getFilter();
/**
* The filter for specifying which tests may be retried.
*/
void filter(Action<? super Filter> action);
/**
* A filter for specifying which tests may be retried.
*
* By default, all tests are eligible for retrying.
*/
interface Filter {
/**
* The patterns used to include tests based on their class name.
*
* The pattern string matches against qualified class names.
* It may contain '*' characters, which match zero or more of any character.
*
* A class name only has to match one pattern to be included.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getIncludeClasses();
/**
* The patterns used to include tests based on their class level annotations.
*
* The pattern string matches against the qualified class names of a test class's annotations.
* It may contain '*' characters, which match zero or more of any character.
*
* A class need only have one annotation matching any of the patterns to be included.
*
* Annotations present on super classes that are {@code @Inherited} are considered when inspecting subclasses.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getIncludeAnnotationClasses();
/**
* The patterns used to exclude tests based on their class name.
*
* The pattern string matches against qualified class names.
* It may contain '*' characters, which match zero or more of any character.
*
* A class name only has to match one pattern to be excluded.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getExcludeClasses();
/**
* The patterns used to exclude tests based on their class level annotations.
*
* The pattern string matches against the qualified class names of a test class's annotations.
* It may contain '*' characters, which match zero or more of any character.
*
* A class need only have one annotation matching any of the patterns to be excluded.
*
* Annotations present on super classes that are {@code @Inherited} are considered when inspecting subclasses.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getExcludeAnnotationClasses();
}
}
----

Expand Down Expand Up @@ -143,6 +221,34 @@ The plugin supports retrying Spock `@Stepwise` tests and TestNG `@Test(dependsOn
* Upstream tests (those that the failed test depends on) are run because a flaky test may depend on state from the prior execution of an upstream test.
* Downstream tests are run because a flaky test causes any downstream tests to be skipped in the initial test run.

== Filtering

By default, all tests are eligible for retrying.
The `filter` component of the test retry extension can be used to control which tests should be retried and which should not.

The decision to retry a test or not is based on the tests reported class name, regardless of the name of the test case or method.
The annotations present or not on this class can also be used as the criteria.

.build.gradle:
[source,groovy]
----
test {
retry {
maxRetries = 3
filter {
// filter by qualified class name (* matches zero or more of any character)
includeClasses.add("*IntegrationTest")
excludeClasses.add("*DatabaseTest")
// filter by class level annotations
// Note: @Inherited annotations are respected
includeAnnotationClasses.add("*Retryable")
excludeAnnotationClasses.add("*NonRetryable")
}
}
}
----

== Reporting

=== Gradle
Expand Down
1 change: 1 addition & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies {
testImplementation(localGroovy())
testImplementation("org.spockframework:spock-core:1.3-groovy-2.5")
testImplementation("net.sourceforge.nekohtml:nekohtml:1.9.22")
testImplementation("org.ow2.asm:asm:8.0.1")

codenarc("org.codenarc:CodeNarc:1.0")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package org.gradle.testretry;

import org.gradle.api.Action;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.testing.Test;

/**
Expand Down Expand Up @@ -67,4 +69,75 @@ public interface TestRetryTaskExtension {
*/
Property<Integer> getMaxFailures();

/**
* The filter for specifying which tests may be retried.
*/
Filter getFilter();

/**
* The filter for specifying which tests may be retried.
*/
void filter(Action<? super Filter> action);

/**
* A filter for specifying which tests may be retried.
*
* By default, all tests are eligible for retrying.
*/
interface Filter {

/**
* The patterns used to include tests based on their class name.
*
* The pattern string matches against qualified class names.
* It may contain '*' characters, which match zero or more of any character.
*
* A class name only has to match one pattern to be included.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getIncludeClasses();

/**
* The patterns used to include tests based on their class level annotations.
*
* The pattern string matches against the qualified class names of a test class's annotations.
* It may contain '*' characters, which match zero or more of any character.
*
* A class need only have one annotation matching any of the patterns to be included.
*
* Annotations present on super classes that are {@code @Inherited} are considered when inspecting subclasses.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getIncludeAnnotationClasses();

/**
* The patterns used to exclude tests based on their class name.
*
* The pattern string matches against qualified class names.
* It may contain '*' characters, which match zero or more of any character.
*
* A class name only has to match one pattern to be excluded.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getExcludeClasses();

/**
* The patterns used to exclude tests based on their class level annotations.
*
* The pattern string matches against the qualified class names of a test class's annotations.
* It may contain '*' characters, which match zero or more of any character.
*
* A class need only have one annotation matching any of the patterns to be excluded.
*
* Annotations present on super classes that are {@code @Inherited} are considered when inspecting subclasses.
*
* If no patterns are specified, all classes (that also meet other configured filters) will be included.
*/
SetProperty<String> getExcludeAnnotationClasses();

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package org.gradle.testretry.internal.config;

import org.gradle.api.Action;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.testretry.TestRetryTaskExtension;

import javax.inject.Inject;
Expand All @@ -26,12 +28,14 @@ public class DefaultTestRetryTaskExtension implements TestRetryTaskExtension {
private final Property<Boolean> failOnPassedAfterRetry;
private final Property<Integer> maxRetries;
private final Property<Integer> maxFailures;
private final Filter filter;

@Inject
public DefaultTestRetryTaskExtension(ObjectFactory objects) {
this.failOnPassedAfterRetry = objects.property(Boolean.class);
this.maxRetries = objects.property(Integer.class);
this.maxFailures = objects.property(Integer.class);
this.filter = new FilterImpl(objects);
}

public Property<Boolean> getFailOnPassedAfterRetry() {
Expand All @@ -46,4 +50,49 @@ public Property<Integer> getMaxFailures() {
return maxFailures;
}

@Override
public void filter(Action<? super Filter> action) {
action.execute(filter);
}

@Override
public Filter getFilter() {
return filter;
}

private static final class FilterImpl implements Filter {

private final SetProperty<String> includeClasses;
private final SetProperty<String> includeAnnotationClasses;
private final SetProperty<String> excludeClasses;
private final SetProperty<String> excludeAnnotationClasses;

public FilterImpl(ObjectFactory objects) {
this.includeClasses = objects.setProperty(String.class);
this.includeAnnotationClasses = objects.setProperty(String.class);
this.excludeClasses = objects.setProperty(String.class);
this.excludeAnnotationClasses = objects.setProperty(String.class);
}

@Override
public SetProperty<String> getIncludeClasses() {
return includeClasses;
}

@Override
public SetProperty<String> getIncludeAnnotationClasses() {
return includeAnnotationClasses;
}

@Override
public SetProperty<String> getExcludeClasses() {
return excludeClasses;
}

@Override
public SetProperty<String> getExcludeAnnotationClasses() {
return excludeAnnotationClasses;
}
}

}
Loading

0 comments on commit 4746bee

Please sign in to comment.