-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use JUnit Platform by default #3053
Conversation
Generate changelog in
|
41b91a2
to
8f9938c
Compare
testingExtension | ||
.getSuites() | ||
.named(JvmTestSuitePlugin.DEFAULT_TEST_SUITE_NAME, JvmTestSuite.class, testSuite -> { | ||
testSuite.useJUnitJupiter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For backwards compatibility reasons, Gradle uses the legacy JUnit4 test toolchain for the default test
test suite and the JUnit Platform test toolchain for everything else.
Here we configure the default test
test suite to also use the JUnit Platform test toolchain.
// See https://github.com/gradle/gradle/pull/21919 | ||
if (GradleVersion.current().compareTo(GRADLE_8) < 0) { | ||
project.getDependencies() | ||
.add(sourceSet.getRuntimeOnlyConfigurationName(), JUNIT_PLATFORM_LAUNCHER); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gradle didn't start automatically adding this dependency until Gradle 8, so for earlier versions of Gradle we replicate that behavior here.
Once we drop support for Gradle 7, this should be removed.
// For test tasks not creating using test suites, we must explicitly the test to use JUnit Platform | ||
// and add the junit-platform-launcher dependency. | ||
if (testingExtension.getSuites().findByName(sourceSet.getName()) == null) { | ||
task.useJUnitPlatform(); | ||
project.getDependencies() | ||
.add(sourceSet.getRuntimeOnlyConfigurationName(), JUNIT_PLATFORM_LAUNCHER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only exists to support the testsets plugin.
Once we migrate users from the testsets plugin to test suites, this should be removed.
} | ||
return Optional.empty(); | ||
} | ||
javaPluginExtension.getSourceSets().configureEach(sourceSet -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a bit weird that we iterate over source sets to find tests. Once we no longer support the testsets plugin (and thus, all tests are declared using test suites), we should change this to iterating over the test suites instead. Same goes for the CheckJUnitDependencies
task.
@Property | ||
void test(@ForAll byte value) {} | ||
} | ||
'''.stripIndent(true) | ||
|
||
def '#gradleVersionNumber: capable of running both junit4 and junit5 tests'() { | ||
def '#gradleVersionNumber: runs JUnit4 tests'() { | ||
when: | ||
gradleVersion = gradleVersionNumber |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only one of these tests was testing on both Gradle 7 and 8. I've updated these tests so we're testing both Gradle versions on all of the tests (except a couple at the bottom where the Gradle version is not relevant).
private Stream<SourceSet> getProbablyTestSourceSets() { | ||
return getProject().getExtensions().getByType(JavaPluginExtension.class).getSourceSets().stream() | ||
.filter(ss -> !ss.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed this filtering because:
- It's inconsistent with the
BaselineTesting
plugin, where we don't filter the source sets. - It's almost certainly unnecessary. If it's a
Test
task, we probably want to perform this validation.
+ ss.getRuntimeOnlyConfigurationName() | ||
+ " 'net.jqwik:jqwik-engine'\n\n"); | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice, we should never reach this unless someone has explicitly configured a test suite to use something other than JUnit Platform. I don't think we care to support any other test frameworks (all the things we we care about integrate with JUnit Platform using a custom engine), so I'm tempted to just remove this.
There may be a small number of repos that require manual changes after this upgrade. Those are repos with projects that exclusively use JUnit4 and are using the legacy JUnit4 test runner (which is the default) and not the JUnit Platform test runner. Given that we performed a pretty extensive JUnit5 migration, the number of internal repos that meet this criteria is quite small (internal code search seems to confirm this, although I can't be certain from textual search alone). It is likely limited to the handful of repos that explicitly disabled the JUnit5 migration. These repos don't have to migrate any of their tests to use JUnit5 APIs - the just need to migrate to use the JUnit Platform test runner (running their JUnit4 tests using the JUnit Vintage engine). Alternatively, these repos can explicitly set the test runner to the JUnit4 test runner. I've verified that this works as expected, removing the JUnit Platorms dependencies rather than adding to them. testing {
suites {
test {
useJUnit()
}
}
} |
9e52ec2
to
17d5daa
Compare
// org.junit.jupiter.api.Test annotation, but as JUnit4 knows nothing about these, they'll silently not run | ||
// unless the user has wired up the dependency correctly. | ||
if (sourceSetMentionsJUnit5Api(ss)) { | ||
String runtime = ss.getRuntimeClasspathConfigurationName(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This produces incorrect suggestions, dependencies should be declared using the runtime only configuration, not the runtime classpath configuration.
gradle-baseline-java/src/main/java/com/palantir/baseline/plugins/BaselineTesting.java
Show resolved
Hide resolved
7da5c0b
to
e2de58a
Compare
private void validateSourceSet(SourceSet ss, Test task) { | ||
Set<ResolvedComponentResult> deps = getProject() | ||
.getConfigurations() | ||
.getByName(ss.getRuntimeClasspathConfigurationName()) | ||
.getIncoming() | ||
.getResolutionResult() | ||
.getAllComponents(); | ||
boolean junitJupiterIsPresent = hasDep(deps, CheckJUnitDependencies::isJunitJupiter); | ||
boolean vintageEngineExists = hasDep(deps, CheckJUnitDependencies::isVintageEngine); | ||
boolean spock1Dependency = hasDep(deps, CheckJUnitDependencies::isSpock1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internal code search shows that there are no remaining users of Spock 1, so I've removed this.
d7e08f4
to
732c800
Compare
732c800
to
1085891
Compare
// We must eagerly create the test task in order to add the junit-platform-launcher dependency. | ||
// Otherwise the dependencies will already have been resolved by the time we create the test task. | ||
testTasks.all(task -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like this might be happening because the configuration to add the dependency happens on the dependencyset rather than test task or on source sets, so lazily configuring the test task/source set will not add the dep at the right time. I wonder if we can configure the dependencies of each Configuration instead - this is pretty much as lazy as possible, only being generated when the Configuration is resolved. Something like:
project.getConfigurations().configureEach(configuration -> {
configuration.getDependencies().addAllLater(project.provider(() -> {
for (Test test : project.getTasks().withType(Test.class)) {
SourceSet sourceSet = sourceSets.getByName(test.getName());
if (testingExtension.getSuites().findByName(sourceSet.getName()) != null) {
return Set.of();
}
if (configuration.getName().equals(sourceSet.getImplementationConfigurationName())) {
return Set.of(project.getDependencies().create(JUNIT_JUPITER));
}
if (configuration.getName().equals(sourceSet.getRuntimeOnlyConfigurationName())) {
return Set.of(project.getDependencies().create(JUNIT_PLATFORM_LAUNCHER));
}
}
return Set.of();
}));
});
If addAllLater
doesn't work (maybe someone iterates over the dependencyset) maybe beforeResolve
will do the trick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we need to do the gradle 8 checking too
task.useJUnitPlatform();
below can happen in a configureEach
now, no need for .all
.
// For test tasks not created using test suites, we must explicitly the test to use JUnit Platform | ||
// and add the junit-platform-launcher dependency. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this effectively just going to be people still using test-sets
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be my expectation, but who knows.
javaPluginExtension.getSourceSets().configureEach(sourceSet -> { | ||
TaskCollection<Test> testTasks = project.getTasks() | ||
.withType(Test.class) | ||
.matching(task -> task.getName().equals(sourceSet.getName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fun fact, I recently discovered that .matching
realises all the tasks immediately, not just .all
:
gradle-baseline-java/src/main/java/com/palantir/baseline/tasks/CheckJUnitDependencies.java
Show resolved
Hide resolved
|
||
public CheckJUnitDependencies() { | ||
setGroup("Verification"); | ||
setDescription("Ensures the correct JUnit4/5 dependencies are present, otherwise tests may silently not run"); | ||
getOutputs().upToDateWhen(_task -> true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tasks with no outputs are never up-to-date
…s/others stuff
👍 |
1 similar comment
👍 |
Thanks for fixing this up! |
👍 |
Released 6.17.0 |
Ran into one small papercut in a repo that does not have any tests. Because it does not have any tests, it does not currently have a Gradle declares a versioned dependency for
The test suites I'm not sure how much effort we should put in to address this - I'm not sure how common it is to have a repo with no tests. The ideal solution is to ensure that every repo has a Alternatively we could unconditionally add a versioned I'm considering putting up a Gradle PR proposing that |
Hmm I think there's something else going on here. This works with a super minimal repro. Maybe GCV is somehow causing issues? plugins {
id 'java'
}
repositories {
mavenCentral()
}
testing {
suites {
test {
useJUnitJupiter()
}
}
}
|
Oh this is because of our internal Will continue the discussion internally. |
The JUnit 5.12.0 upgrade is causing test failures in all of our repos. See some prior discussion in junit-team/junit5#4335.
The Gradle documentation states that, in the absence of test suites, the necessary test framework dependencies (ex.
junit-platform-launcher
) must be explicitly declared. I think we've just gotten lucky that nothing in the JUnit implementation has actually required this dependency until now.The best solution here is to migrate
BaselineTesting
to configure tests using the newerjvm-test-suites
plugin, which is applied automatically be thejava
plugin and already used to configured the defaulttest
test suite. The test suites plugin will automatically add the necessary test framework dependencies, and consumers can use their existing version management tool (ex. gradle-consistent-versions) to ensure consistent versions of the JUnit dependencies.Consumers will just need to add a
org.junit.platform:*
constraints in theirversions.props
. We can easily automate this (this is already done).