Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/testing-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,70 @@ If not specified, tests run against the default version (currently `8.14.3`).

See [Resolution of Gradle versions to test against](../README.md#resolution-of-gradle-versions-to-test-against) for more details.

#### Adding Versions for Specific Tests

Use `@WithGradleVersions` to add extra Gradle versions for a specific test class or individual test methods.

> **Note:** This annotation is intended for exceptional cases where a specific test needs additional versions.
> For configuring Gradle versions across your entire test suite, prefer setting versions in the `gradle/gradle-test-versions.yml` file.

```java
@GradlePluginTests
@WithGradleVersions({"7.6.5", "8.0"})
class CompatibilityTest {
@Test
void works_on_older_gradle_versions(GradleInvoker gradle, RootProject project) {
// This test runs against the globally configured versions PLUS 7.6.5 and 8.0
}

@Test
@WithGradleVersions("8.5")
void test_specific_version(GradleInvoker gradle, RootProject project) {
// This test runs against globally configured versions PLUS 7.6.5, 8.0 (from class), and 8.5 (from method)
}
}
```

The versions from `@WithGradleVersions` are merged with the globally configured versions. When applied to both a class and a method, all versions are combined. Duplicate versions are automatically deduplicated.

#### Filtering to Specific Versions

Use `@WithOnlyGradleVersions` to filter the test matrix to only run on specific Gradle versions. Unlike `@WithGradleVersions` which adds versions, this annotation restricts which versions from the matrix will actually run.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name @WithOnlyGradleVersions could imply an authoritative list of versions you run the test with. This can be confusing.

Copy link
Copy Markdown
Contributor Author

@FinlayRJW FinlayRJW Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about RestrictToGradleVersionsEqualTo I think that implies it only filters?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then we can add RestrictToGradleVersionsGreaterThan etc later


```java
@GradlePluginTests
class FilteredVersionTest {
@Test
@WithOnlyGradleVersions(value = "8.14.3", reason = "This test only applies to Gradle 8.14.3")
void test_only_on_specific_version(GradleInvoker gradle, RootProject project) {
// This test only runs on 8.14.3, even if other versions are in the matrix
}

@Test
@WithOnlyGradleVersions({"8.10", "8.14.3"})
void test_on_subset_of_versions(GradleInvoker gradle, RootProject project) {
// This test only runs on 8.10 and 8.14.3
}
}
```

**Key differences from `@WithGradleVersions`:**
- `@WithGradleVersions` **adds** versions to the test matrix
- `@WithOnlyGradleVersions` **filters** the existing matrix to only include specified versions

**Important:** If you specify a version that isn't in the test matrix, the test simply won't run for that version. To run a specific version that isn't in the matrix, use both annotations together:

```java
@Test
@WithGradleVersions("8.5") // Add 8.5 to the matrix
@WithOnlyGradleVersions("8.5") // Filter to only run 8.5
void test_only_on_8_5(GradleInvoker gradle, RootProject project) {
// Runs exclusively on Gradle 8.5
}
```

The annotation can be applied at the class level to filter all tests in the class, or at the method level for individual tests. Method-level filters are applied in addition to class-level filters.

## File Operations

### Working with Files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.palantir.gradle.testing.junit;

import com.google.common.base.Splitter;
import com.palantir.gradle.testing.execution.GradleVersion;
import java.util.List;
import java.util.stream.Stream;
Expand All @@ -34,14 +33,7 @@ public boolean supportsClassTemplate(ExtensionContext context) {
@Override
public Stream<? extends ClassTemplateInvocationContext> provideClassTemplateInvocationContexts(
ExtensionContext context) {
String gradleVersionsToTestAgainst = context.getConfigurationParameter(
"com.palantir.gradle.testing.gradle_versions_to_test")
.orElseThrow(() -> new RuntimeException("Not configured with the gradle versions to test against. "
+ "Have you applied the `com.palantir.gradle-plugin-testing` plugin to this project?"));

List<String> gradleVersions = Splitter.on(',').splitToList(gradleVersionsToTestAgainst);

return gradleVersions.stream().map(GradleVersion::new).map(GradleVersionInvocationContext::new);
return GradleVersions.allFilteredVersions(context).stream().map(GradleVersionInvocationContext::new);
}

private record GradleVersionInvocationContext(GradleVersion gradleVersion)
Expand All @@ -59,6 +51,7 @@ public void prepareInvocation(ExtensionContext context) {
@Override
public List<Extension> getAdditionalExtensions() {
return List.of(
new WithGradleVersionsCondition(),
new GradleInvokerParameterResolver(),
new GradleProjectParameterResolver(),
new MavenRepoParameterResolver());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* (c) Copyright 2025 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.gradle.testing.junit;

import com.google.common.base.Splitter;
import com.palantir.gradle.testing.execution.GradleVersion;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.support.AnnotationSupport;

/**
* Utility class for Gradle versions based on annotations.
*/
final class GradleVersions {

private static final String GRADLE_VERSIONS_CONFIG_PARAM = "com.palantir.gradle.testing.gradle_versions_to_test";

/**
* Returns all Gradle versions for the test class, including versions from all methods,
* with the class-level {@link WithOnlyGradleVersions} filter applied.
*
* <p>This is used to build the complete test matrix for the class.
*/
static Set<GradleVersion> allFilteredVersions(ExtensionContext context) {
Set<GradleVersion> versions = configuredVersions(context);

context.getTestClass().ifPresent(clazz -> {
versions.addAll(versionsFromAnnotation(clazz));

Arrays.stream(clazz.getDeclaredMethods())
.filter(GradleVersions::isTestMethod)
.forEach(method -> versions.addAll(versionsFromAnnotation(method)));

applyFilter(versions, clazz);
});

return versions;
}

/**
* Returns all Gradle versions for a specific method, including class-level versions,
* with the method-level {@link WithOnlyGradleVersions} filter applied.
*
* <p>This is used to determine if a method should run for a given Gradle version.
*/
static Set<GradleVersion> filteredVersionsForMethod(ExtensionContext context) {
Set<GradleVersion> versions = configuredVersions(context);

context.getTestClass().ifPresent(clazz -> versions.addAll(versionsFromAnnotation(clazz)));

context.getTestMethod().ifPresent(method -> {
versions.addAll(versionsFromAnnotation(method));
applyFilter(versions, method);
});

return versions;
}

private static Set<GradleVersion> configuredVersions(ExtensionContext context) {
return new LinkedHashSet<>(context.getConfigurationParameter(GRADLE_VERSIONS_CONFIG_PARAM)
.map(param -> Splitter.on(',').splitToList(param).stream()
.map(GradleVersion::new)
.toList())
.orElseThrow(() -> new RuntimeException("Not configured with the gradle versions to test against. "
+ "Have you applied the `com.palantir.gradle-plugin-testing` plugin to this project?")));
}

private static Set<GradleVersion> versionsFromAnnotation(AnnotatedElement element) {
return AnnotationSupport.findAnnotation(element, WithGradleVersions.class).stream()
.flatMap(annotation -> Arrays.stream(annotation.value()))
.map(GradleVersion::new)
.collect(Collectors.toCollection(LinkedHashSet::new));
}

private static void applyFilter(Set<GradleVersion> versions, AnnotatedElement element) {
AnnotationSupport.findAnnotation(element, WithOnlyGradleVersions.class).ifPresent(annotation -> {
Set<GradleVersion> allowed =
Arrays.stream(annotation.value()).map(GradleVersion::new).collect(Collectors.toSet());
versions.retainAll(allowed);
});
}

private static boolean isTestMethod(Method method) {
return AnnotationSupport.isAnnotated(method, Test.class);
}

private GradleVersions() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* (c) Copyright 2025 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.gradle.testing.junit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for adding additional Gradle versions to individual test classes or methods.
*
* <p><b>Important:</b> This annotation is intended for exceptional cases where a specific test or test class
* needs to run against additional Gradle versions beyond the globally configured versions. For configuring
* Gradle versions across your entire test suite, prefer setting versions in the
* {@code gradle/gradle-test-versions.yml} file
*
* <p>The versions specified in this annotation will be merged with the versions configured in the yml file.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithGradleVersions {

/**
* The additional Gradle versions to test against.
* @return an array of Gradle version strings (e.g., "7.6.5", "8.0")
*/
String[] value();

/**
* Optional reason explaining why these additional Gradle versions are needed.
* @return the reason for requiring these specific versions
*/
String reason() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* (c) Copyright 2025 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.gradle.testing.junit;

import com.palantir.gradle.testing.execution.GradleVersion;
import java.util.Set;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;

/**
* Execution condition that filters tests based on Gradle version.
*
* <p>When a method has its own {@link WithGradleVersions} annotation, this condition ensures
* that only the method-specific versions (plus base and class-level versions) run for that method.
* For methods without the annotation, only base and class-level versions run.
*
* <p>When a method has filter annotations like {@link WithOnlyGradleVersions}, the allowed versions
* are filtered accordingly. Class-level filter annotations are handled in {@link GradleVersioningClassTemplate}
* to filter the test matrix upfront.
*/
final class WithGradleVersionsCondition implements ExecutionCondition {

@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (context.getTestMethod().isEmpty()) {
return ConditionEvaluationResult.enabled("No test method");
}

GradleVersion currentVersion = GradleVersionStore.gradleVersion(context);
Set<GradleVersion> versionsForThisMethod = GradleVersions.filteredVersionsForMethod(context);

if (versionsForThisMethod.contains(currentVersion)) {
return ConditionEvaluationResult.enabled(
"Gradle version " + currentVersion + " is in the allowed set for this method");
}

return ConditionEvaluationResult.disabled("Gradle version " + currentVersion
+ " is not in the allowed set for this method: " + versionsForThisMethod);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* (c) Copyright 2025 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.gradle.testing.junit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for filtering Gradle versions to only run the specified versions.
*
* <p>Unlike {@link WithGradleVersions} which adds versions to the test matrix, this annotation filters the
* available versions to only include the specified ones. If a specified version is not in the test matrix
* (from configuration or {@code @WithGradleVersions}), it will simply not run.
*
* <p>To run a specific version that isn't in the matrix, use both annotations:
* {@code @WithGradleVersions("8.5")} and {@code @WithOnlyGradleVersions("8.5")}.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithOnlyGradleVersions {

/**
* The Gradle versions to filter to.
* @return an array of Gradle version strings (e.g., "7.6.5", "8.0")
*/
String[] value();

/**
* Optional reason explaining why only these specific Gradle versions should run.
* @return the reason for filtering to these specific versions
*/
String reason() default "";
}
Loading