[junit6] support for running junit6 IDEA-378235

(cherry picked from commit d8150843a509548fe1f04b8871a469a212584f0f)

IJ-MR-175463

GitOrigin-RevId: 59e5f52d5653e7bbac6ee4f9b9fdb62d12732f68
This commit is contained in:
Aleksey Dobrynin
2025-09-25 17:51:29 +00:00
committed by intellij-monorepo-bot
parent aeab189eb4
commit e97d627f6d
76 changed files with 2997 additions and 177 deletions
+38
View File
@@ -0,0 +1,38 @@
<component name="libraryTable">
<library name="JUnit6" type="repository">
<properties maven-id="org.junit.jupiter:junit-jupiter-api:6.0.0-RC2">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/6.0.0-RC2/junit-jupiter-api-6.0.0-RC2.jar">
<sha256sum>db1404414d23effbeb1202025f1e984f9069ddeed6d4c1826bb70bce8c70fb31</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar">
<sha256sum>48e2df636cab6563ced64dcdff8abb2355627cb236ef0bf37598682ddf742f1b</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/6.0.0-RC2/junit-platform-commons-6.0.0-RC2.jar">
<sha256sum>7f7d65576d02547d4d6b44ec3d49dd37904cd8927baae5bddd541cd24d513a39</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar">
<sha256sum>b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar">
<sha256sum>1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/6.0.0-RC2/junit-jupiter-api-6.0.0-RC2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/6.0.0-RC2/junit-platform-commons-6.0.0-RC2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/6.0.0-RC2/junit-jupiter-api-6.0.0-RC2-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/6.0.0-RC2/junit-platform-commons-6.0.0-RC2-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+33
View File
@@ -0,0 +1,33 @@
<component name="libraryTable">
<library name="JUnit6Launcher" type="repository">
<properties maven-id="org.junit.platform:junit-platform-launcher:6.0.0-RC2">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/6.0.0-RC2/junit-platform-launcher-6.0.0-RC2.jar">
<sha256sum>3c41ec667a185ec0b51ca8d9d68e5f0e425d6645c6e4eb64b8a81716a4deaed1</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/6.0.0-RC2/junit-platform-engine-6.0.0-RC2.jar">
<sha256sum>8f5e8cb552c311d4a236334975a5ff9ec4db5f64e84151d01500f31fa19edfba</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar">
<sha256sum>1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab</sha256sum>
</artifact>
</verification>
<exclude>
<dependency maven-id="org.apiguardian:apiguardian-api" />
<dependency maven-id="org.opentest4j:opentest4j" />
<dependency maven-id="org.junit.platform:junit-platform-commons" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/6.0.0-RC2/junit-platform-launcher-6.0.0-RC2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/6.0.0-RC2/junit-platform-engine-6.0.0-RC2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/6.0.0-RC2/junit-platform-launcher-6.0.0-RC2-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/6.0.0-RC2/junit-platform-engine-6.0.0-RC2-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+2
View File
@@ -590,6 +590,8 @@
<module fileurl="file://$PROJECT_DIR$/plugins/junit/testFramework/intellij.junit.testFramework.iml" filepath="$PROJECT_DIR$/plugins/junit/testFramework/intellij.junit.testFramework.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/junit5_rt/intellij.junit.v5.rt.iml" filepath="$PROJECT_DIR$/plugins/junit5_rt/intellij.junit.v5.rt.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/junit5_rt_tests/intellij.junit.v5.rt.tests.iml" filepath="$PROJECT_DIR$/plugins/junit5_rt_tests/intellij.junit.v5.rt.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/junit6_rt/intellij.junit.v6.rt.iml" filepath="$PROJECT_DIR$/plugins/junit6_rt/intellij.junit.v6.rt.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/junit6_rt_tests/intellij.junit.v6.rt.tests.iml" filepath="$PROJECT_DIR$/plugins/junit6_rt_tests/intellij.junit.v6.rt.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/jvm/jvm-analysis-api/intellij.jvm.analysis.iml" filepath="$PROJECT_DIR$/jvm/jvm-analysis-api/intellij.jvm.analysis.iml" />
<module fileurl="file://$PROJECT_DIR$/jvm/jvm-analysis-impl/intellij.jvm.analysis.impl.iml" filepath="$PROJECT_DIR$/jvm/jvm-analysis-impl/intellij.jvm.analysis.impl.iml" />
<module fileurl="file://$PROJECT_DIR$/jvm/jvm-analysis-internal-testFramework/intellij.jvm.analysis.internal.testFramework.iml" filepath="$PROJECT_DIR$/jvm/jvm-analysis-internal-testFramework/intellij.jvm.analysis.internal.testFramework.iml" />
+2
View File
@@ -885,6 +885,8 @@ plugins/junit/kotlin-tests-shared
plugins/junit/testFramework
plugins/junit5_rt
plugins/junit5_rt_tests
plugins/junit6_rt
plugins/junit6_rt_tests
plugins/junit_rt
plugins/keymaps/eclipse-keymap
plugins/keymaps/netbeans5.6-keymap
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.execution.ExecutionBundle;
@@ -71,7 +71,7 @@ public class InheritorChooser {
}
if (classes.size() == 1) {
runForClass(classes.get(0), psiMethod, context, performRunnable);
runForClass(classes.getFirst(), psiMethod, context, performRunnable);
return true;
}
if (classes.isEmpty()) return false;
@@ -83,7 +83,7 @@ public class InheritorChooser {
final List<PsiClass> psiClasses = new ArrayList<>(Arrays.asList(((PsiClassOwner)containingFile).getClasses()));
psiClasses.retainAll(classes);
if (psiClasses.size() == 1) {
runForClass(psiClasses.get(0), psiMethod, context, performRunnable);
runForClass(psiClasses.getFirst(), psiMethod, context, performRunnable);
return true;
}
}
@@ -107,7 +107,7 @@ public class InheritorChooser {
classes.sort(renderer.getComparator());
//suggest to run all inherited tests
classes.add(0, null);
classes.addFirst(null);
String locationName = psiMethod != null ? psiMethod.getName() : containingClass.getName();
JBPopupFactory.getInstance()
.createPopupChooserBuilder(classes)
@@ -137,7 +137,7 @@ public class InheritorChooser {
return false;
}
private static void collectClasses(PsiClass containingClass, List<? super PsiClass> classes, HashSet<? super PsiClass> visited) {
private static void collectClasses(PsiClass containingClass, List<? super PsiClass> classes, Set<? super PsiClass> visited) {
if (!visited.add(containingClass)) return;
Set<PsiClass> containers = new HashSet<>();
final boolean isJUnit5 = ReadAction.compute(() -> JUnitUtil.isJUnit5(containingClass));
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.codeInsight.AnnotationUtil;
@@ -38,7 +38,7 @@ public final class JUnitUtil {
public static final String RULE_ANNOTATION = "org.junit.Rule";
public static final String TEST5_PACKAGE_FQN = "org.junit.jupiter.api";
public static final String TEST5_ANNOTATION = "org.junit.jupiter.api.Test";
public static final String CUSTOM_TESTABLE_ANNOTATION = "org.junit.platform.commons.annotation.Testable";
public static final Set<String> CUSTOM_TESTABLE_ANNOTATION_LIST = Collections.singleton(CUSTOM_TESTABLE_ANNOTATION);
@@ -304,7 +304,6 @@ public final class JUnitUtil {
return true;
}
public static boolean isJUnit5TestClass(final @NotNull PsiClass psiClass, boolean checkAbstract) {
final PsiModifierList modifierList = psiClass.getModifierList();
if (modifierList == null) return false;
@@ -68,6 +68,7 @@ public final @NonNls class JUnitCommonClassNames {
public static final String ORG_JUNIT_JUPITER_API_EXTENSION_EXTEND_WITH = "org.junit.jupiter.api.extension.ExtendWith";
public static final String ORG_JUNIT_JUPITER_API_TEST_INSTANCE = "org.junit.jupiter.api.TestInstance";
public static final String ORG_JUNIT_PLATFORM_ENGINE_TEST_ENGINE = "org.junit.platform.engine.TestEngine";
public static final String ORG_JUNIT_PLATFORM_ENGINE_CANCELLATION_TOKEN = "org.junit.platform.engine.CancellationToken";
public static final String ORG_JUNIT_PLATFORM_ENGINE = "org.junit.platform.engine";
public static final String ORG_JUNIT_JUPITER_API_EXTENSION_REGISTER_EXTENSION = "org.junit.jupiter.api.extension.RegisterExtension";
public static final String ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_ALL_CALLBACK = "org.junit.jupiter.api.extension.BeforeAllCallback";
@@ -83,4 +84,7 @@ public final @NonNls class JUnitCommonClassNames {
public static final String ORG_JUNIT_EXPERIMENTAL_THEORIES_DATAPOINTS = "org.junit.experimental.theories.DataPoints";
public static final String ORG_JUNIT_EXPERIMENTAL_RUNNERS_ENCLOSED = "org.junit.experimental.runners.Enclosed";
public static final String ORG_JUNIT_JUPITER_API_IO_TEMPDIR = "org.junit.jupiter.api.io.TempDir";
public static final String ORG_JUNIT_PLATFORM_SUITE_API_SUITE = "org.junit.platform.suite.api.Suite";
// junit 6
public static final String ORG_JUNIT_JUPITER_API_METHOD_ORDERER_DEFAULT = "org.junit.jupiter.api.MethodOrderer.Default";
}
@@ -30,6 +30,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
@@ -42,15 +43,19 @@ public abstract class JavaTestFramework implements JvmTestFramework {
public boolean isLibraryAttached(@NotNull Module module) {
GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module);
Project project = module.getProject();
PsiClass c = DumbService.getInstance(project).computeWithAlternativeResolveEnabled(() -> JavaPsiFacade.getInstance(project).findClass(getMarkerClassFQName(), scope));
return c != null;
return DumbService.getInstance(project).computeWithAlternativeResolveEnabled(() -> {
for (String markerClassFQName : getMarkerClassFQNames()) {
if (JavaPsiFacade.getInstance(project).findClass(markerClassFQName, scope) != null) return true;
}
return false;
});
}
@Override
public @Nullable String getLibraryPath() {
ExternalLibraryDescriptor descriptor = getFrameworkLibraryDescriptor();
if (descriptor != null) {
return descriptor.getLibraryClassesRoots().get(0);
return descriptor.getLibraryClassesRoots().getFirst();
}
return null;
}
@@ -60,14 +65,24 @@ public abstract class JavaTestFramework implements JvmTestFramework {
return null;
}
protected Collection<String> getMarkerClassFQNames() {
return Collections.singleton(getMarkerClassFQName());
}
/**
* @deprecated Use {@link #getMarkerClassFQNames()} instead
*/
@Deprecated
protected abstract String getMarkerClassFQName();
/**
* Return {@code true} iff {@link #getMarkerClassFQName()} can be found in the resolve scope of {@code clazz}
* Return {@code true} iff {@link #getMarkerClassFQNames()} can be found in the resolve scope of {@code clazz}
*/
public boolean isFrameworkAvailable(@NotNull PsiElement clazz) {
String markerClassFQName = getMarkerClassFQName();
return isFrameworkApplicable(clazz, markerClassFQName);
for (String markerClassFQName : getMarkerClassFQNames()) {
if (isFrameworkApplicable(clazz, markerClassFQName)) return true;
}
return false;
}
protected static boolean isFrameworkApplicable(@NotNull PsiElement clazz, String markerClassFQName) {
+66 -6
View File
@@ -3246,6 +3246,72 @@ java_library(
visibility = ["//visibility:public"]
)
java_library(
name = "junit6",
exports = [
":junit-jupiter-api-6_0_0-RC2_http_import",
":opentest4j-1_3_0_http_import",
":junit-platform-commons-6_0_0-RC2_http_import",
":apiguardian-api-1_1_2_http_import",
":jspecify-1_0_0_http_import",
],
visibility = ["//visibility:public"]
)
jvm_import(
name = "junit-jupiter-api-6_0_0-RC2_http_import",
jar = "@junit-jupiter-api-6_0_0-RC2_http//file",
source_jar = "@junit-jupiter-api-6_0_0-RC2-sources_http//file"
)
jvm_import(
name = "junit-platform-commons-6_0_0-RC2_http_import",
jar = "@junit-platform-commons-6_0_0-RC2_http//file",
source_jar = "@junit-platform-commons-6_0_0-RC2-sources_http//file"
)
jvm_import(
name = "jspecify-1_0_0_http_import",
jar = "@jspecify-1_0_0_http//file",
source_jar = "@jspecify-1_0_0-sources_http//file"
)
java_library(
name = "junit6-provided",
exports = [":junit6"],
neverlink = True,
visibility = ["//visibility:public"]
)
java_library(
name = "junit6Launcher",
exports = [
":junit-platform-launcher-6_0_0-RC2_http_import",
":junit-platform-engine-6_0_0-RC2_http_import",
":jspecify-1_0_0_http_import",
],
visibility = ["//visibility:public"]
)
jvm_import(
name = "junit-platform-launcher-6_0_0-RC2_http_import",
jar = "@junit-platform-launcher-6_0_0-RC2_http//file",
source_jar = "@junit-platform-launcher-6_0_0-RC2-sources_http//file"
)
jvm_import(
name = "junit-platform-engine-6_0_0-RC2_http_import",
jar = "@junit-platform-engine-6_0_0-RC2_http//file",
source_jar = "@junit-platform-engine-6_0_0-RC2-sources_http//file"
)
java_library(
name = "junit6Launcher-provided",
exports = [":junit6Launcher"],
neverlink = True,
visibility = ["//visibility:public"]
)
jvm_import(
name = "jvm-native-trusted-roots",
jar = "@jvm-native-trusted-roots-1_1_7_http//file",
@@ -4557,12 +4623,6 @@ jvm_import(
source_jar = "@lifecycle-common-jvm-2_9_2-sources_http//file"
)
jvm_import(
name = "jspecify-1_0_0_http_import",
jar = "@jspecify-1_0_0_http//file",
source_jar = "@jspecify-1_0_0-sources_http//file"
)
jvm_import(
name = "lifecycle-runtime-desktop-2_9_2_http_import",
jar = "@lifecycle-runtime-desktop-2_9_2_http//file",
+70 -14
View File
@@ -4605,6 +4605,76 @@ http_file(
downloaded_file_path = "junit-vintage-engine-5.13.4-sources.jar"
)
http_file(
name = "junit-jupiter-api-6_0_0-RC2_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/6.0.0-RC2/junit-jupiter-api-6.0.0-RC2.jar",
sha256 = "db1404414d23effbeb1202025f1e984f9069ddeed6d4c1826bb70bce8c70fb31",
downloaded_file_path = "junit-jupiter-api-6.0.0-RC2.jar"
)
http_file(
name = "junit-platform-commons-6_0_0-RC2_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/6.0.0-RC2/junit-platform-commons-6.0.0-RC2.jar",
sha256 = "7f7d65576d02547d4d6b44ec3d49dd37904cd8927baae5bddd541cd24d513a39",
downloaded_file_path = "junit-platform-commons-6.0.0-RC2.jar"
)
http_file(
name = "jspecify-1_0_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar",
sha256 = "1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab",
downloaded_file_path = "jspecify-1.0.0.jar"
)
http_file(
name = "junit-jupiter-api-6_0_0-RC2-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/6.0.0-RC2/junit-jupiter-api-6.0.0-RC2-sources.jar",
sha256 = "4addc5bf25b7ecdd2b24b2710632fca9854410cc96d5e2332c0ac3b123ebb080",
downloaded_file_path = "junit-jupiter-api-6.0.0-RC2-sources.jar"
)
http_file(
name = "junit-platform-commons-6_0_0-RC2-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/6.0.0-RC2/junit-platform-commons-6.0.0-RC2-sources.jar",
sha256 = "e1469584f6c28fcf4eaf44dc3fd300bbeb51912f4052edbef7ee5ee0a28ff744",
downloaded_file_path = "junit-platform-commons-6.0.0-RC2-sources.jar"
)
http_file(
name = "jspecify-1_0_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jspecify/jspecify/1.0.0/jspecify-1.0.0-sources.jar",
sha256 = "adf0898191d55937fb3192ba971826f4f294292c4a960740f3c27310e7b70296",
downloaded_file_path = "jspecify-1.0.0-sources.jar"
)
http_file(
name = "junit-platform-launcher-6_0_0-RC2_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/6.0.0-RC2/junit-platform-launcher-6.0.0-RC2.jar",
sha256 = "3c41ec667a185ec0b51ca8d9d68e5f0e425d6645c6e4eb64b8a81716a4deaed1",
downloaded_file_path = "junit-platform-launcher-6.0.0-RC2.jar"
)
http_file(
name = "junit-platform-engine-6_0_0-RC2_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/6.0.0-RC2/junit-platform-engine-6.0.0-RC2.jar",
sha256 = "8f5e8cb552c311d4a236334975a5ff9ec4db5f64e84151d01500f31fa19edfba",
downloaded_file_path = "junit-platform-engine-6.0.0-RC2.jar"
)
http_file(
name = "junit-platform-launcher-6_0_0-RC2-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/6.0.0-RC2/junit-platform-launcher-6.0.0-RC2-sources.jar",
sha256 = "f100a0b9b4249e8f050375b47875b91dba01c41fa226dfb31c94c3bdc9de0f72",
downloaded_file_path = "junit-platform-launcher-6.0.0-RC2-sources.jar"
)
http_file(
name = "junit-platform-engine-6_0_0-RC2-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/6.0.0-RC2/junit-platform-engine-6.0.0-RC2-sources.jar",
sha256 = "6b725abf25a6688bf413980ab07ecb2423cf093fea08a607eef01afa7f6a457d",
downloaded_file_path = "junit-platform-engine-6.0.0-RC2-sources.jar"
)
http_file(
name = "jvm-native-trusted-roots-1_1_7_http",
url = "https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-dependencies/org/jetbrains/nativecerts/jvm-native-trusted-roots/1.1.7/jvm-native-trusted-roots-1.1.7.jar",
@@ -6418,13 +6488,6 @@ http_file(
downloaded_file_path = "lifecycle-common-jvm-2.9.2.jar"
)
http_file(
name = "jspecify-1_0_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar",
sha256 = "1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab",
downloaded_file_path = "jspecify-1.0.0.jar"
)
http_file(
name = "lifecycle-runtime-desktop-2_9_2_http",
url = "https://cache-redirector.jetbrains.com/dl.google.com/dl/android/maven2/androidx/lifecycle/lifecycle-runtime-desktop/2.9.2/lifecycle-runtime-desktop-2.9.2.jar",
@@ -6586,13 +6649,6 @@ http_file(
downloaded_file_path = "lifecycle-common-jvm-2.9.2-sources.jar"
)
http_file(
name = "jspecify-1_0_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jspecify/jspecify/1.0.0/jspecify-1.0.0-sources.jar",
sha256 = "adf0898191d55937fb3192ba971826f4f294292c4a960740f3c27310e7b70296",
downloaded_file_path = "jspecify-1.0.0-sources.jar"
)
http_file(
name = "lifecycle-runtime-desktop-2_9_2-sources_http",
url = "https://cache-redirector.jetbrains.com/dl.google.com/dl/android/maven2/androidx/lifecycle/lifecycle-runtime-desktop/2.9.2/lifecycle-runtime-desktop-2.9.2-sources.jar",
@@ -10,7 +10,9 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
// in java - don't use kotlin to avoid loading non-JDK classes
@ApiStatus.Internal
@@ -84,21 +86,15 @@ public final class JUnitDevMainKt {
String result = null;
String junitRtJar = File.separator + "junit-rt.jar";
String junitRtPackage = File.separator + "intellij.junit.rt";
for (String s : testEntryPointClasspath.split(File.pathSeparator)) {
if (s.endsWith(junitRtJar) || s.endsWith(junitRtPackage)) {
result = s;
break;
}
}
String junit5RtJar = File.separator + "junit5-rt.jar";
String junit5RtPackage = File.separator + "intellij.junit.v5.rt";
for (String s : testEntryPointClasspath.split(File.pathSeparator)) {
if (s.endsWith(junit5RtJar) || s.endsWith(junit5RtPackage)) {
result = result == null ? s : result + File.pathSeparator + s;
break;
List<String> versions = Arrays.asList("", "5", "6");
for (String version : versions) {
String junitRtJar = File.separator + "junit" + version + "-rt.jar";
String junitRtPackage = File.separator + "intellij.junit" + (version.isEmpty() ? "" : ".v" + version) + ".rt";
for (String s : testEntryPointClasspath.split(File.pathSeparator)) {
if (s.endsWith(junitRtJar) || s.endsWith(junitRtPackage)) {
result = result == null ? s : result + File.pathSeparator + s;
break;
}
}
}
@@ -154,6 +154,7 @@ object CommunityRepositoryModules {
pluginAuto("intellij.junit") { spec ->
spec.withModule("intellij.junit.rt", "junit-rt.jar")
spec.withModule("intellij.junit.v5.rt", "junit5-rt.jar")
spec.withModule("intellij.junit.v6.rt", "junit6-rt.jar")
},
plugin("intellij.testng") { spec ->
spec.mainJarName = "testng-plugin.jar"
@@ -318,7 +318,6 @@ public class PersistentFS_FindFilesTest {
}
finally {
pool.shutdown();
//noinspection ResultOfMethodCallIgnored
pool.awaitTermination(1, MINUTES);
}
}
@@ -371,6 +371,7 @@ de.plushnikov.intellij.plugin.*
com.intellij.junit4.*
com.intellij.testng.integration.*
com.intellij.testDiscovery.*
com.intellij.execution.junit.*
[PYTHON_TESTS]
com.jetbrains.python.*
@@ -108,7 +108,7 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
"modulePath/prod1", module);
ModuleRootModificationUtil.updateModel(module, model -> {
ContentEntry entry = model.getContentEntries()[0];
entry.removeSourceFolder(entry.getSourceFolders(JavaSourceRootType.SOURCE).get(0));
entry.removeSourceFolder(entry.getSourceFolders(JavaSourceRootType.SOURCE).getFirst());
});
JavaParameters params4Tests = configuration.getTestObject().createJavaParameters4Tests();
assertEmpty(params4Tests.getModulePath().getPathList());
@@ -128,7 +128,7 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
PathsList modulePath = params4Tests.getModulePath();
assertTrue("module path: " + modulePath.getPathsString(),
modulePath.getPathList().stream().anyMatch(filePath -> filePath.contains("junit-jupiter-api")));
ContainerUtil.exists(modulePath.getPathList(), filePath -> filePath.contains("junit-jupiter-api")));
//production module output is not on the module path
assertFalse("module path: " + modulePath.getPathsString(),
modulePath.getPathList().contains(getCompilerOutputPath(module)));
@@ -138,7 +138,7 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
assertSize(2, modulePath.getPathList());
//launcher should be put on the classpath
assertTrue(params4Tests.getClassPath().getPathList().stream().anyMatch(filePath -> filePath.contains("launcher")));
assertTrue(ContainerUtil.exists(params4Tests.getClassPath().getPathList(), filePath -> filePath.contains("launcher")));
}
public void testModuleInfoInTestModularizedJunit() throws Exception {
@@ -153,8 +153,8 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
" --add-modules org.junit.platform.launcher", moduleOptions.getParametersList().getParametersString());
PathsList classPath = params4Tests.getClassPath();
assertContainsElements(classPath.getPathList(), PathUtil.getJarPathForClass(JUnitStarter.class));
assertContainsElements(classPath.getPathList(), ContainerUtil.map(TestObject.getJUnit5RtFiles(), File::getPath));
assertContainsElements(classPath.getPathList(), TestObject.getJUnitRtPath().getPath());
assertContainsElements(classPath.getPathList(), ContainerUtil.map(TestObject.getJUnitRtFiles(JUnitStarter.JUNIT5_PARAMETER), File::getPath));
PathsList modulePath = params4Tests.getModulePath();
checkLibrariesOnPathList(module, modulePath);
@@ -165,7 +165,7 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
//test module output is on the module path
assertTrue("module path: " + modulePath.getPathsString(),
modulePathList.contains(getCompilerOutputPath(module, true)));
assertTrue(modulePathList.stream().anyMatch(filePath -> filePath.contains("launcher")));
assertTrue(ContainerUtil.exists(modulePathList, filePath -> filePath.contains("launcher")));
}
public void testModuleInfoInTestModularizedJunitDependencyOnAnotherModule() throws Exception {
@@ -176,7 +176,7 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
model.addModuleOrderEntry(module1);
});
JpsMavenRepositoryLibraryDescriptor nonModularizedJupiterDescription =
new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", "5.5.2");
new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", "6.0.0-RC2");
JUnitConfiguration configuration = setupConfiguration(nonModularizedJupiterDescription, "modulePath/test2", module);
JavaParameters params4Tests = configuration.getTestObject().createJavaParameters4Tests();
ParamsGroup moduleOptions = JavaTestFrameworkRunnableState.getJigsawOptions(params4Tests);
@@ -185,8 +185,8 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
" --add-modules org.junit.platform.launcher", moduleOptions.getParametersList().getParametersString());
PathsList classPath = params4Tests.getClassPath();
assertContainsElements(classPath.getPathList(), PathUtil.getJarPathForClass(JUnitStarter.class));
assertContainsElements(classPath.getPathList(), ContainerUtil.map(TestObject.getJUnit5RtFiles(), File::getPath));
assertContainsElements(classPath.getPathList(), TestObject.getJUnitRtPath().getPath());
assertContainsElements(classPath.getPathList(), ContainerUtil.map(TestObject.getJUnitRtFiles(JUnitStarter.JUNIT6_PARAMETER), File::getPath));
PathsList modulePath = params4Tests.getModulePath();
checkLibrariesOnPathList(module, modulePath);
@@ -198,7 +198,7 @@ public class JUnitModulePathTest extends BaseConfigurationTestCase {
//test module output is on the module path
assertTrue("module path: " + modulePath.getPathsString(),
modulePathList.contains(getCompilerOutputPath(module, true)));
assertTrue(modulePathList.stream().anyMatch(filePath -> filePath.contains("launcher")));
assertTrue(ContainerUtil.exists(modulePathList, filePath -> filePath.contains("launcher")));
}
private JUnitConfiguration setupConfiguration(JpsMavenRepositoryLibraryDescriptor libraryDescriptor, String sources, Module module) throws Exception {
+4 -1
View File
@@ -6,4 +6,7 @@
- name: intellij.junit
- name: lib/junit5-rt.jar
modules:
- name: intellij.junit.v5.rt
- name: intellij.junit.v5.rt
- name: lib/junit6-rt.jar
modules:
- name: intellij.junit.v6.rt
+3 -1
View File
@@ -28,7 +28,8 @@
<cantBeStatic implementation="com.intellij.execution.junit2.inspection.JUnitCantBeStaticExtension" />
<testFramework implementation="com.intellij.execution.junit.JUnit4Framework" id="junit4"/>
<testFramework implementation="com.intellij.execution.junit.JUnit3Framework" id="junit3" order="after junit4"/>
<testFramework implementation="com.intellij.execution.junit.JUnit5Framework" id="junit5" order="after junit3"/>
<testFramework implementation="com.intellij.execution.junit.JUnit6Framework" id="junit6" order="after junit3"/>
<testFramework implementation="com.intellij.execution.junit.JUnit5Framework" id="junit5" order="after junit6"/>
<configurationType implementation="com.intellij.execution.junit.JUnitConfigurationType"/>
<library.dependencyScopeSuggester implementation="com.intellij.execution.junit.JUnitDependencyScopeSuggester"/>
<externalSystem.runConfigurationImporter implementation="com.intellij.execution.junit.JUnitRunConfigurationImporter"/>
@@ -49,6 +50,7 @@
<stacktrace.fold substring="at com.intellij.junit3."/>
<stacktrace.fold substring="at com.intellij.junit4."/>
<stacktrace.fold substring="at com.intellij.junit5."/>
<stacktrace.fold substring="at com.intellij.junit6."/>
<stacktrace.fold substring="at com.intellij.rt.junit."/>
<stacktrace.fold substring="at junit.framework.TestSuite.run"/>
<stacktrace.fold substring="at junit.framework.TestCase.run"/>
@@ -0,0 +1,4 @@
@org.junit.jupiter.api.AfterAll
static void afterAll() {
${BODY}
}
@@ -0,0 +1,27 @@
<!-- Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
<html>
<body>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates an afterAll method in a JUnit 6 test class.</font>
</td>
</tr>
</table>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">Name of the created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">Generated method body</font></td>
</tr>
</table>
</body>
</html>
@@ -0,0 +1,4 @@
@org.junit.jupiter.api.BeforeAll
static void beforeAll() {
${BODY}
}
@@ -0,0 +1,27 @@
<!-- Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
<html>
<body>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a beforeAll method in a JUnit 6 test class.</font>
</td>
</tr>
</table>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">Name of the created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">generated method body</font></td>
</tr>
</table>
</body>
</html>
@@ -0,0 +1,4 @@
@org.junit.jupiter.api.BeforeEach
void setUp() {
${BODY}
}
@@ -0,0 +1,26 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a setUp method in a JUnit 6 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated method body</font></td>
</tr>
</table>
</body>
</html>
@@ -0,0 +1,4 @@
@org.junit.jupiter.api.AfterEach
void tearDown() {
${BODY}
}
@@ -0,0 +1,26 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a tearDown method in a JUnit 6 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated method body</font></td>
</tr>
</table>
</body>
</html>
@@ -0,0 +1,5 @@
import static org.junit.jupiter.api.Assertions.*;
#parse("File Header.java")
class ${NAME} {
${BODY}
}
@@ -0,0 +1,31 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a JUnit 6 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${CLASS_NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the tested class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
</tr>
</table>
</body>
</html>
@@ -0,0 +1,4 @@
@org.junit.jupiter.api.Test
void ${NAME}() {
${BODY}
}
@@ -0,0 +1,26 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a test method in a JUnit 6 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated method body</font></td>
</tr>
</table>
</body>
</html>
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.codeInsight.AnnotationUtil;
@@ -16,6 +16,11 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import static com.siyeh.ig.junit.JUnitCommonClassNames.ORG_JUNIT_PLATFORM_SUITE_API_SUITE;
public class JUnit5Framework extends JUnitTestFramework {
@Override
@@ -29,17 +34,13 @@ public class JUnit5Framework extends JUnitTestFramework {
}
@Override
protected String getMarkerClassFQName() {
return JUnitUtil.TEST5_ANNOTATION;
protected Collection<String> getMarkerClassFQNames() {
return List.of(JUnitUtil.TEST5_ANNOTATION, JUnitUtil.CUSTOM_TESTABLE_ANNOTATION);
}
@Override
public boolean isFrameworkAvailable(@NotNull PsiElement clazz) {
return callWithAlternateResolver(clazz.getProject(), ()->{
return isFrameworkApplicable(clazz, JUnitUtil.CUSTOM_TESTABLE_ANNOTATION) ||
//check explicit jupiter to support library with broken dependency
isFrameworkApplicable(clazz, JUnitUtil.TEST5_ANNOTATION);
}, false);
protected String getMarkerClassFQName() {
return JUnitUtil.TEST5_ANNOTATION;
}
@Override
@@ -196,7 +197,7 @@ public class JUnit5Framework extends JUnitTestFramework {
public boolean isSuiteClass(PsiClass psiClass) {
if (psiClass == null) return false;
return callWithAlternateResolver(psiClass.getProject(), () -> {
return AnnotationUtil.isAnnotated(psiClass, "org.junit.platform.suite.api.Suite", AnnotationUtil.CHECK_HIERARCHY);
return AnnotationUtil.isAnnotated(psiClass, ORG_JUNIT_PLATFORM_SUITE_API_SUITE, AnnotationUtil.CHECK_HIERARCHY);
}, false);
}
}
@@ -0,0 +1,204 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
import com.intellij.execution.JUnitBundle;
import com.intellij.ide.fileTemplates.FileTemplateDescriptor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ExternalLibraryDescriptor;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import static com.siyeh.ig.junit.JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_METHOD_ORDERER_DEFAULT;
import static com.siyeh.ig.junit.JUnitCommonClassNames.ORG_JUNIT_PLATFORM_SUITE_API_SUITE;
public class JUnit6Framework extends JUnitTestFramework {
@Override
public boolean isDumbAware() {
return this.getClass().isAssignableFrom(JUnit6Framework.class);
}
@Override
public @NotNull String getName() {
return "JUnit6";
}
@Override
protected Collection<String> getMarkerClassFQNames() {
return List.of(ORG_JUNIT_JUPITER_API_METHOD_ORDERER_DEFAULT, "org.junit.platform.commons.annotation.Contract");
}
@Override
protected String getMarkerClassFQName() {
return ORG_JUNIT_JUPITER_API_METHOD_ORDERER_DEFAULT;
}
@Override
public boolean shouldRunSingleClassAsJUnit5(Project project, GlobalSearchScope scope) {
return true;
}
@Override
public @Nullable ExternalLibraryDescriptor getFrameworkLibraryDescriptor() {
return JUnitExternalLibraryDescriptor.JUNIT6;
}
@Override
public @Nullable String getDefaultSuperClass() {
return null;
}
@Override
public boolean isTestClass(PsiClass clazz, boolean canBePotential) {
return callWithAlternateResolver(clazz.getProject(), () -> {
if (canBePotential) return isUnderTestSources(clazz);
if (!isFrameworkAvailable(clazz)) return false;
return JUnitUtil.isJUnit5TestClass(clazz, false);
}, false);
}
@Override
protected @Nullable PsiMethod findSetUpMethod(@NotNull PsiClass clazz) {
return callWithAlternateResolver(clazz.getProject(), ()->{
for (PsiMethod each : clazz.getMethods()) {
if (AnnotationUtil.isAnnotated(each, JUnitUtil.BEFORE_EACH_ANNOTATION_NAME, 0)) return each;
}
return null;
}, null);
}
@Override
protected @Nullable PsiMethod findBeforeClassMethod(@NotNull PsiClass clazz) {
return callWithAlternateResolver(clazz.getProject(), () -> {
for (PsiMethod each : clazz.getMethods()) {
if (each.hasModifierProperty(PsiModifier.STATIC)
&& AnnotationUtil.isAnnotated(each, JUnitUtil.BEFORE_ALL_ANNOTATION_NAME, 0)) return each;
}
return null;
}, null);
}
@Override
protected @Nullable PsiMethod findTearDownMethod(@NotNull PsiClass clazz) {
return callWithAlternateResolver(clazz.getProject(), () -> {
for (PsiMethod each : clazz.getMethods()) {
if (AnnotationUtil.isAnnotated(each, JUnitUtil.AFTER_EACH_ANNOTATION_NAME, 0)) return each;
}
return null;
}, null);
}
@Override
protected @Nullable PsiMethod findAfterClassMethod(@NotNull PsiClass clazz) {
return callWithAlternateResolver(clazz.getProject(), () -> {
for (PsiMethod each : clazz.getMethods()) {
if (each.hasModifierProperty(PsiModifier.STATIC)
&& AnnotationUtil.isAnnotated(each, JUnitUtil.AFTER_ALL_ANNOTATION_NAME, 0)) {
return each;
}
}
return null;
}, null);
}
@Override
protected @Nullable PsiMethod findOrCreateSetUpMethod(PsiClass clazz) throws IncorrectOperationException {
PsiMethod method = findSetUpMethod(clazz);
if (method != null) return method;
PsiManager manager = clazz.getManager();
PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
method = createSetUpPatternMethod(factory);
PsiMethod existingMethod = clazz.findMethodBySignature(method, false);
if (existingMethod != null) {
if (AnnotationUtil.isAnnotated(existingMethod, JUnitUtil.BEFORE_ALL_ANNOTATION_NAME, 0)) return existingMethod;
int exit = ApplicationManager.getApplication().isUnitTestMode() ?
Messages.OK :
Messages.showOkCancelDialog(JUnitBundle.message("create.setup.dialog.message", "@BeforeEach"),
JUnitBundle.message("create.setup.dialog.title"),
Messages.getWarningIcon());
if (exit == Messages.OK) {
AddAnnotationPsiFix.addPhysicalAnnotationIfAbsent(JUnitUtil.BEFORE_EACH_ANNOTATION_NAME, PsiNameValuePair.EMPTY_ARRAY,
existingMethod.getModifierList());
return existingMethod;
}
}
final PsiMethod testMethod = JUnitUtil.findFirstTestMethod(clazz);
if (testMethod != null) {
method = (PsiMethod)clazz.addBefore(method, testMethod);
} else {
method = (PsiMethod)clazz.add(method);
}
JavaCodeStyleManager.getInstance(manager.getProject()).shortenClassReferences(method);
return method;
}
@Override
public boolean isIgnoredMethod(PsiElement element) {
if (element == null) return false;
return callWithAlternateResolver(element.getProject(), () -> {
if (element instanceof PsiMethod method) {
final PsiMethod ignoredTestMethod =
AnnotationUtil.isAnnotated(method, "org.junit.jupiter.api.Disabled", 0) ? JUnitUtil.getTestMethod(element) : null;
return ignoredTestMethod != null;
}
return false;
}, true);
}
@Override
public boolean acceptNestedClasses() {
return true;
}
@Override
public FileTemplateDescriptor getSetUpMethodFileTemplateDescriptor() {
return new FileTemplateDescriptor("JUnit6 SetUp Method.java");
}
@Override
public FileTemplateDescriptor getBeforeClassMethodFileTemplateDescriptor() {
return new FileTemplateDescriptor("JUnit6 BeforeAll Method.java");
}
@Override
public FileTemplateDescriptor getTearDownMethodFileTemplateDescriptor() {
return new FileTemplateDescriptor("JUnit6 TearDown Method.java");
}
@Override
public FileTemplateDescriptor getAfterClassMethodFileTemplateDescriptor() {
return new FileTemplateDescriptor("JUnit6 AfterAll Method.java");
}
@Override
public @NotNull FileTemplateDescriptor getTestMethodFileTemplateDescriptor() {
return new FileTemplateDescriptor("JUnit6 Test Method.java");
}
@Override
public FileTemplateDescriptor getTestClassFileTemplateDescriptor() {
return new FileTemplateDescriptor("JUnit6 Test Class.java");
}
@Override
public boolean isSuiteClass(PsiClass psiClass) {
if (psiClass == null) return false;
return callWithAlternateResolver(psiClass.getProject(), () -> {
return AnnotationUtil.isAnnotated(psiClass, ORG_JUNIT_PLATFORM_SUITE_API_SUITE, AnnotationUtil.CHECK_HIERARCHY);
}, false);
}
}
@@ -1,25 +1,22 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.openapi.roots.ExternalLibraryDescriptor;
import org.jetbrains.annotations.NotNull;
public final class JUnitExternalLibraryDescriptor extends ExternalLibraryDescriptor {
public static final ExternalLibraryDescriptor JUNIT3 = new JUnitExternalLibraryDescriptor("3", "3.8.2");
public static final ExternalLibraryDescriptor JUNIT4 = new JUnitExternalLibraryDescriptor("4", "4.13.1");
public static final ExternalLibraryDescriptor JUNIT5 = new JUnitExternalLibraryDescriptor("org.junit.jupiter", "junit-jupiter", "5.8.1",
"5.8.1");
public static final ExternalLibraryDescriptor JUNIT3 = new JUnitExternalLibraryDescriptor("junit", "junit", "3", "3.8.2", "3.0");
public static final ExternalLibraryDescriptor JUNIT4 = new JUnitExternalLibraryDescriptor("junit", "junit", "4", "4.13.1", "4.0");
public static final ExternalLibraryDescriptor JUNIT5 = new JUnitExternalLibraryDescriptor("org.junit.jupiter", "junit-jupiter", "5", "5.13.4", "5.0.0");
public static final ExternalLibraryDescriptor JUNIT6 = new JUnitExternalLibraryDescriptor("org.junit.jupiter", "junit-jupiter", "6", "6.0.0-RC2", "6.0.0-RC2");
private final String myVersion;
private JUnitExternalLibraryDescriptor(String baseVersion, String preferredVersion) {
this("junit", "junit", baseVersion, preferredVersion);
}
private JUnitExternalLibraryDescriptor(final String groupId,
final String artifactId,
final String version,
String preferredVersion) {
super(groupId, artifactId, version + ".0", version + ".999", preferredVersion);
private JUnitExternalLibraryDescriptor(@NotNull String groupId,
@NotNull String artifactId,
@NotNull String version,
@NotNull String preferredVersion,
@NotNull String minVersion) {
super(groupId, artifactId, minVersion, version + ".999", preferredVersion);
myVersion = version;
}
@@ -42,8 +42,7 @@ import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
@@ -82,6 +81,10 @@ import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import static com.intellij.execution.junit.JUnitExternalLibraryDescriptor.JUNIT5;
import static com.intellij.execution.junit.JUnitExternalLibraryDescriptor.JUNIT6;
import static com.siyeh.ig.junit.JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_METHOD_ORDERER_DEFAULT;
public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitConfiguration> implements PossiblyDumbAware {
private static final String LAUNCHER_MODULE_NAME = "org.junit.platform.launcher";
private static final String JUPITER_ENGINE_NAME = "org.junit.jupiter.engine";
@@ -100,6 +103,14 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
private final Map<Module, JavaParameters> myAdditionalJarsForModuleFork = new HashMap<>();
private static final Map<String, String> RUNNER_VERSIONS = Map.of(
JUnitStarter.JUNIT3_PARAMETER, "3",
JUnitStarter.JUNIT4_PARAMETER, "4",
JUnitStarter.JUNIT5_PARAMETER, "5",
JUnitStarter.JUNIT6_PARAMETER, "6"
);
protected static final Set<String> JUPITER_RUNNERS = Set.of(JUnitStarter.JUNIT5_PARAMETER, JUnitStarter.JUNIT6_PARAMETER);
protected TestObject(JUnitConfiguration configuration, ExecutionEnvironment environment) {
super(environment);
myConfiguration = configuration;
@@ -180,7 +191,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
Set<Module> result = new HashSet<>();
final SourceScope sourceScope = getSourceScope();
final Project project = getConfiguration().getProject();
if (sourceScope != null && packageName != null && JUnitStarter.JUNIT5_PARAMETER.equals(getRunner())) {
if (sourceScope != null && packageName != null && JUPITER_RUNNERS.contains(getRunner())) {
final PsiPackage aPackage = JavaPsiFacade.getInstance(getConfiguration().getProject()).findPackage(packageName);
if (aPackage != null) {
final TestSearchScope scope = getScope();
@@ -235,10 +246,12 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
throw new CantRunException(JavaBundle.message("error.message.ide.does.not.support.starting.processes.using.old.java",
jdkVersion.getDescription()));
}
javaParameters.getClassPath().addFirst(path != null ? path : PathUtil.getJarPathForClass(JUnitStarter.class));
javaParameters.getClassPath().addFirst(path != null ? path : getJUnitRtPath().getAbsolutePath());
//include junit5 listeners for the case custom junit 5 engines would be detected on runtime
javaParameters.getClassPath().addAllFiles(getJUnit5RtFiles());
javaParameters.getClassPath().addAllFiles(getJUnitRtFiles(JUnitStarter.JUNIT5_PARAMETER));
//include junit6 listeners for the case custom junit 6 engines would be detected on runtime
javaParameters.getClassPath().addAllFiles(getJUnitRtFiles(JUnitStarter.JUNIT6_PARAMETER));
appendDownloadedDependenciesForForkedConfigurations(javaParameters, module);
}
@@ -261,30 +274,43 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
}
public static List<File> getJUnit5RtFiles() {
File junit4Rt = new File(PathUtil.getJarPathForClass(JUnit4IdeaTestRunner.class));
public static List<File> getJUnitRtFiles(@NotNull String runner) {
String version = RUNNER_VERSIONS.getOrDefault(runner, "5");
File junit4Rt = getJUnitRtPath();
String junit4Name = junit4Rt.getName();
String junit5Name;
String junitCurrentName;
if (junit4Rt.isDirectory()) {
junit5Name = junit4Name.replace("junit", "junit.v5");
junitCurrentName = junit4Name.replace("junit", "junit.v" + version);
}
else {
var relevantJarsRoot = ArchivedCompilationContextUtil.getArchivedCompiledClassesLocation();
Map<String, List<String>> mapping = ArchivedCompilationContextUtil.getArchivedCompiledClassesMapping();
if (relevantJarsRoot != null && junit4Rt.toPath().startsWith(relevantJarsRoot) && mapping != null) {
return ContainerUtil.map(mapping.get("production/intellij.junit.v5.rt"), File::new);
return ContainerUtil.map(mapping.get("production/intellij.junit.v" + version + ".rt"), File::new);
}
else {
junit5Name = junit4Name.replace("junit", "junit5");
junitCurrentName = junit4Name.replace("junit", "junit" + version);
}
}
return Collections.singletonList(new File(junit4Rt.getParent(), junit5Name));
return Collections.singletonList(new File(junit4Rt.getParent(), junitCurrentName));
}
public static File getJUnitRtPath() {
String currentPath = PathUtil.getJarPathForClass(TestObject.class);
String currentUrl = VfsUtil.getUrlForLibraryRoot(new File(currentPath));
if (StandardFileSystems.FILE_PROTOCOL.equals(VirtualFileManager.extractProtocol(currentUrl))) {
File rtDir = new File(new File(currentPath).getParentFile(), "intellij.junit.rt");
if (rtDir.exists()) {
return rtDir;
}
}
return new File(PathUtil.getJarPathForClass(JUnit4IdeaTestRunner.class));
}
/**
* Junit 5 searches for tests in the classpath.
* When 2 modules have e.g. the same package, one depends on another, and tests have to run in single module only,
* by configuration settings or to avoid repetition in fork by module mode, additional filters per output directories are required.
* Junit 5/6 searches for tests in the classpath.
* When 2 modules have e.g. the same package, one depends on another, and tests have to run in a single module only,
* by configuration settings or to avoid repetition in fork by module mode; additional filters per output directories are required.
*/
protected static @Unmodifiable List<String> composeDirectoryFilter(@NotNull Module module) {
return ContainerUtil.map(OrderEnumerator.orderEntries(module)
@@ -296,6 +322,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
@Override
protected JavaParameters createJavaParameters() throws ExecutionException {
JavaParameters javaParameters = super.createJavaParameters();
if (javaParameters.getMainClass() == null) { // for custom main class, e.g. overridden by JUnitDevKitUnitTestingSettings.Companion#apply
javaParameters.setMainClass(JUnitConfiguration.JUNIT_START_CLASS);
}
@@ -329,17 +356,17 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
return parameters;
}
public void appendJUnit5LauncherClasses(JavaParameters javaParameters,
Project project,
GlobalSearchScope globalSearchScope,
boolean ensureOnModulePath) throws CantRunException {
public void appendJUnitLauncherClasses(String runnerName,
JavaParameters javaParameters,
Project project,
GlobalSearchScope globalSearchScope,
boolean ensureOnModulePath) throws CantRunException {
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
DumbService dumbService = DumbService.getInstance(project);
String launcherVersion = getLibraryVersion("org.junit.platform.commons.JUnitException", globalSearchScope, project);
if (launcherVersion == null) {
LOG.info("Failed to detect junit 5 launcher version, please configure explicit dependency");
LOG.info("Failed to detect junit " + RUNNER_VERSIONS.getOrDefault(runnerName, "5") + " launcher version, please configure explicit dependency");
return;
}
@@ -350,7 +377,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
.isEmpty()).executeSynchronously() &&
VersionComparatorUtil.compare(launcherVersion, "1.5.0") >= 0;
if (isModularized) { //for modularized junit ensure launcher is included in the module graph
if (isModularized) { //for modularized junit ensure the launcher is included in the module graph
ParamsGroup group = getOrCreateJigsawOptions(javaParameters);
ParametersList vmParametersList = group.getParametersList();
if (!vmParametersList.hasParameter(LAUNCHER_MODULE_NAME)) {
@@ -368,8 +395,12 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
//add standard engines only if no engine api is present
if (!hasJUnit5EnginesAPI(globalSearchScope, psiFacade) || !isCustomJUnit5(globalSearchScope)) {
String jupiterVersion = ObjectUtils.notNull(getLibraryVersion(JUnitUtil.TEST5_ANNOTATION, globalSearchScope, project), "5.0.0");
if (!hasJupiterEnginesAPI(globalSearchScope, psiFacade) || !isCustomJUnit(globalSearchScope,
JUnitStarter.JUNIT6_PARAMETER.equals(runnerName)
? JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_CANCELLATION_TOKEN
: JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_TEST_ENGINE)) {
String defaultMinVersion = JUnitStarter.JUNIT6_PARAMETER.equals(runnerName) ? JUNIT6.getMinVersion() : JUNIT5.getMinVersion();
String jupiterVersion = ObjectUtils.notNull(getLibraryVersion(JUnitUtil.TEST5_ANNOTATION, globalSearchScope, project), defaultMinVersion);
if (JUnitUtil.hasPackageWithDirectories(psiFacade, JUnitUtil.TEST5_PACKAGE_FQN, globalSearchScope)) {
if (!JUnitUtil.hasPackageWithDirectories(psiFacade, JUPITER_ENGINE_NAME, globalSearchScope)) {
downloadDependenciesWhenRequired(project, additionalDependencies,
@@ -382,7 +413,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
if (JUnitUtil.hasPackageWithDirectories(psiFacade, "org.junit.platform.suite.api", globalSearchScope)) {
if (!JUnitUtil.hasPackageWithDirectories(psiFacade, SUITE_ENGINE_NAME, globalSearchScope)) {
String suiteVersion = getLibraryVersion("org.junit.platform.suite.api.Suite", globalSearchScope, project);
String suiteVersion = getLibraryVersion(JUnitCommonClassNames.ORG_JUNIT_PLATFORM_SUITE_API_SUITE, globalSearchScope, project);
if (suiteVersion != null && VersionComparatorUtil.compare(suiteVersion, "1.8.0") >= 0) {
downloadDependenciesWhenRequired(project, additionalDependencies,
new RepositoryLibraryProperties("org.junit.platform", "junit-platform-suite-engine", suiteVersion));
@@ -433,8 +464,8 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
/**
* junit 4.12+ must be on the classpath for vintage engine to work correctly.
* Don't add engine when it will fail to detect tests anyway.
* junit 4.12+ must be on the classpath for the vintage engine to work correctly.
* Don't add engine when it fails to detect tests anyway.
* <p>
* Reflection is needed for the case when no sources are attached
*/
@@ -452,7 +483,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
}
public static boolean hasJUnit5EnginesAPI(GlobalSearchScope globalSearchScope, JavaPsiFacade psiFacade) {
public static boolean hasJupiterEnginesAPI(GlobalSearchScope globalSearchScope, JavaPsiFacade psiFacade) {
return JUnitUtil.hasPackageWithDirectories(psiFacade, "org.junit.platform.engine", globalSearchScope);
}
@@ -548,7 +579,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
@Override
protected boolean isIdBasedTestTree() {
return JUnitStarter.JUNIT5_PARAMETER.equals(getRunner());
return JUPITER_RUNNERS.contains(getRunner());
}
@Override
@@ -571,14 +602,14 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
super.downloadAdditionalDependencies(javaParameters);
String preferredRunner = getRunner();
if (JUnitStarter.JUNIT5_PARAMETER.equals(preferredRunner)) {
if (JUPITER_RUNNERS.contains(preferredRunner)) {
JUnitConfiguration configuration = getConfiguration();
final Project project = configuration.getProject();
Module module = configuration.getConfigurationModule().getModule();
ThrowableComputable<Void, ExecutionException> downloader = () -> {
appendJUnit5LauncherClasses(javaParameters, project,
getScopeForJUnit(module, project),
useModulePath() && module != null && ReadAction.compute(() -> findJavaModule(module, true)) != null);
appendJUnitLauncherClasses(preferredRunner, javaParameters, project,
getScopeForJUnit(module, project),
useModulePath() && module != null && ReadAction.compute(() -> findJavaModule(module, true)) != null);
if (forkPerModule()) {
for (Module packageModule : ReadAction.compute(() -> collectPackageModules(configuration.getPackage()))) {
JavaParameters parameters = new JavaParameters();
@@ -587,12 +618,9 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
parameters.getVMParametersList().addParamsGroup(group.clone());
}
parameters.setJdk(javaParameters.getJdk());
appendJUnit5LauncherClasses(parameters,
project,
getScopeForJUnit(packageModule, project),
useModulePath() &&
packageModule != null &&
ReadAction.compute(() -> findJavaModule(packageModule, true)) != null);
appendJUnitLauncherClasses(preferredRunner, parameters, project,
getScopeForJUnit(packageModule, project),
useModulePath() && packageModule != null && ReadAction.compute(() -> findJavaModule(packageModule, true)) != null);
myAdditionalJarsForModuleFork.put(packageModule, parameters);
}
}
@@ -728,6 +756,20 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
}
private String getRunner(@NotNull GlobalSearchScope scope, @NotNull Project project) {
if (JavaPsiFacade.getInstance(project).findClass(ORG_JUNIT_JUPITER_API_METHOD_ORDERER_DEFAULT, scope) != null ||
isCustomJUnit(scope, JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_CANCELLATION_TOKEN)) {
return JUnitStarter.JUNIT6_PARAMETER;
}
else if (JUnitUtil.isJUnit5(scope, project) ||
isCustomJUnit(scope, JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_TEST_ENGINE)) {
return JUnitStarter.JUNIT5_PARAMETER;
}
else {
return DEFAULT_RUNNER;
}
}
private @NotNull String getRunnerInner() {
Project project = myConfiguration.getProject();
LOG.assertTrue(!DumbService.getInstance(project).isAlternativeResolveEnabled());
@@ -737,8 +779,9 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
return JUnitStarter.JUNIT4_PARAMETER;
}
if (JUnitConfiguration.TEST_TAGS.equals(data.TEST_OBJECT)) {
return JUnitStarter.JUNIT5_PARAMETER;
return getRunner(globalSearchScope, project);
}
boolean isMethodConfiguration = JUnitConfiguration.TEST_METHOD.equals(data.TEST_OBJECT);
boolean isClassConfiguration = JUnitConfiguration.TEST_CLASS.equals(data.TEST_OBJECT);
final PsiClass psiClass = isMethodConfiguration || isClassConfiguration
@@ -746,6 +789,10 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
if (psiClass != null) {
Set<TestFramework> testFrameworks = TestFrameworks.detectApplicableFrameworks(psiClass);
TestFramework testFramework = ContainerUtil.getFirstItem(testFrameworks);
if (testFramework instanceof JUnit6Framework ||
testFrameworks.size() > 1 && ContainerUtil.find(testFrameworks, f -> f instanceof JUnit6Framework) != null) {
return JUnitStarter.JUNIT6_PARAMETER;
}
if (testFramework instanceof JUnit5Framework ||
testFrameworks.size() > 1 && ContainerUtil.find(testFrameworks, f -> f instanceof JUnit5Framework) != null) {
return JUnitStarter.JUNIT5_PARAMETER;
@@ -760,28 +807,29 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
return JUnitStarter.JUNIT4_PARAMETER;
}
}
return JUnitUtil.isJUnit5(globalSearchScope, project) || isCustomJUnit5(globalSearchScope) ? JUnitStarter.JUNIT5_PARAMETER : DEFAULT_RUNNER;
return getRunner(globalSearchScope, project);
}
private boolean isCustomJUnit5(GlobalSearchScope globalSearchScope) {
private boolean isCustomJUnit(GlobalSearchScope globalSearchScope, String jupiterClassName) {
Project project = myConfiguration.getProject();
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
Boolean isCustomJUnit5UsingPsi =
Boolean isCustomJUnitUsingPsi =
ReadAction.nonBlocking(() -> {
if (DumbService.isDumb(project)) {
return null;
}
return findCustomJunit5TestEngineUsingPsi(globalSearchScope, project, psiFacade);
return hasCustomJupiterTestEngineUsingPsi(globalSearchScope, project, psiFacade, jupiterClassName);
}).executeSynchronously();
if (isCustomJUnit5UsingPsi != null) {
return isCustomJUnit5UsingPsi;
if (isCustomJUnitUsingPsi != null) {
return isCustomJUnitUsingPsi;
}
return findCustomJUnit5TestEngineUsingClassLoader(globalSearchScope, psiFacade);
return findCustomJupiterTestEngineUsingClassLoader(globalSearchScope, psiFacade);
}
private boolean findCustomJUnit5TestEngineUsingClassLoader(@NotNull GlobalSearchScope globalSearchScope,
@NotNull JavaPsiFacade psiFacade) {
private boolean findCustomJupiterTestEngineUsingClassLoader(@NotNull GlobalSearchScope globalSearchScope,
@NotNull JavaPsiFacade psiFacade) {
boolean hasPlatformEngine = ReadAction.compute(() -> {
PsiPackage aPackage = psiFacade.findPackage(JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE);
return aPackage != null && aPackage.getDirectories(globalSearchScope).length > 0;
@@ -793,7 +841,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
serviceLoader = ServiceLoader.load(Class.forName(JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_TEST_ENGINE, false, loader), loader);
for (Object engine : serviceLoader) {
String engineClassName = engine.getClass().getName();
if (isCustomJunit5TestEngineName(engineClassName)) {
if (isCustomJupiterTestEngineName(engineClassName)) {
return true;
}
}
@@ -804,10 +852,11 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
}
private static boolean findCustomJunit5TestEngineUsingPsi(@NotNull GlobalSearchScope globalSearchScope,
private static boolean hasCustomJupiterTestEngineUsingPsi(@NotNull GlobalSearchScope globalSearchScope,
@NotNull Project project,
@NotNull JavaPsiFacade psiFacade) {
PsiClass testEngine = psiFacade.findClass(JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_TEST_ENGINE, globalSearchScope);
@NotNull JavaPsiFacade psiFacade,
@NotNull String jupiterClassName) {
PsiClass testEngine = psiFacade.findClass(jupiterClassName, globalSearchScope);
if (testEngine == null) return false;
Collection<VirtualFile> files = FilenameIndex.getVirtualFilesByName(PsiJavaModule.MODULE_INFO_FILE, globalSearchScope);
if (!files.isEmpty() && ReferencesSearch.search(testEngine, GlobalSearchScope.filesScope(project, files)).anyMatch(ref -> isCustomEngineProvided(testEngine, ref))) {
@@ -815,7 +864,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
}
PsiManager psiManager = PsiManager.getInstance(project);
GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes(globalSearchScope, SPIFileType.INSTANCE);
return FilenameIndex.getVirtualFilesByName(JUnitCommonClassNames.ORG_JUNIT_PLATFORM_ENGINE_TEST_ENGINE, scope)
return FilenameIndex.getVirtualFilesByName(jupiterClassName, scope)
.stream()
.map(f -> psiManager.findFile(f))
.filter(Objects::nonNull)
@@ -823,7 +872,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
.map(r -> r.resolve())
.filter(e -> e instanceof PsiClass)
.map(e -> (PsiClass)e)
.filter(c -> isCustomJunit5TestEngineName(c.getQualifiedName()))
.filter(c -> isCustomJupiterTestEngineName(c.getQualifiedName()))
.anyMatch(c -> InheritanceUtil.isInheritorOrSelf(c, testEngine, true));
}
@@ -838,7 +887,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState<JUnitCon
return false;
}
private static boolean isCustomJunit5TestEngineName(@Nullable String engineImplClassName) {
private static boolean isCustomJupiterTestEngineName(@Nullable String engineImplClassName) {
return !"org.junit.jupiter.engine.JupiterTestEngine".equals(engineImplClassName) &&
!"org.junit.vintage.engine.VintageTestEngine".equals(engineImplClassName);
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.execution.*;
@@ -67,7 +67,7 @@ public class TestPackage extends TestObject {
}
@Override
public @Nullable SearchForTestsTask createSearchingForTestsTask(@NotNull TargetEnvironment remoteEnvironment) throws ExecutionException {
public @Nullable SearchForTestsTask createSearchingForTestsTask(@NotNull TargetEnvironment remoteEnvironment) {
final JUnitConfiguration.Data data = getConfiguration().getPersistentData();
final Module module = getConfiguration().getConfigurationModule().getModule();
return new SearchForTestsTask(getConfiguration().getProject(), getServerSocket()) {
@@ -78,7 +78,7 @@ public class TestPackage extends TestObject {
myClasses.clear();
final SourceScope sourceScope = getSourceScope();
if (sourceScope != null) {
if (JUnitStarter.JUNIT5_PARAMETER.equals(getRunner())) {
if (JUPITER_RUNNERS.contains(getRunner())) {
searchTests5(module, myClasses);
}
else {
@@ -95,7 +95,7 @@ public class TestPackage extends TestObject {
try {
String packageName = getPackageName(data);
String filters = getFilters(myClasses, packageName);
if (JUnitStarter.JUNIT5_PARAMETER.equals(getRunner()) && module != null && filterOutputByDirectoryForJunit5(myClasses)) {
if (JUPITER_RUNNERS.contains(getRunner()) && module != null && filterOutputByDirectoryForJunit5(myClasses)) {
JUnitStarter.printClassesList(composeDirectoryFilter(getModuleWithTestsToFilter(module)), packageName, "", filters, myTempFile);
}
else {
@@ -148,7 +148,7 @@ public class TestPackage extends TestObject {
}
protected boolean requiresSmartMode() {
return !JUnitStarter.JUNIT5_PARAMETER.equals(getRunner());
return !JUPITER_RUNNERS.contains(getRunner());
}
protected boolean filterOutputByDirectoryForJunit5(final Set<Location<?>> classNames) {
@@ -167,7 +167,7 @@ public class TestPackage extends TestObject {
collectClassesRecursively(classFilter, acceptClassCondition, classes);
}
else {
LinkedHashSet<PsiClass> psiClasses = new LinkedHashSet<>();
Set<PsiClass> psiClasses = new LinkedHashSet<>();
ConfigurationUtil.findAllTestClasses(classFilter, module, psiClasses);
psiClasses.stream().map(PsiLocation::fromPsiElement).forEach(classes::add);
}
@@ -198,7 +198,7 @@ public class TestPackage extends TestObject {
final JUnitConfiguration.Data data = getConfiguration().getPersistentData();
final Project project = getConfiguration().getProject();
final SourceScope sourceScope = data.getScope().getSourceScope(getConfiguration());
if (sourceScope == null || !JUnitStarter.JUNIT5_PARAMETER.equals(getRunner())) { //check for junit 5
if (sourceScope == null || !JUPITER_RUNNERS.contains(getRunner())) { //check for junit 5/6
ReadAction.run(() -> JUnitUtil.checkTestCase(sourceScope, project));
}
createTempFiles(javaParameters);
@@ -94,8 +94,8 @@ public final class UniqueIdConfigurationProducer extends JUnitConfigurationProdu
Project project = context.getProject();
GlobalSearchScope searchScope =
module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : GlobalSearchScope.projectScope(project);
if (!DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode(() -> JUnitUtil.isJUnit5(searchScope, project) ||
TestObject.hasJUnit5EnginesAPI(searchScope, JavaPsiFacade.getInstance(project)))) {
if (!DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode(() -> JUnitUtil.isJUnit5(searchScope, project) ||
TestObject.hasJupiterEnginesAPI(searchScope, JavaPsiFacade.getInstance(project)))) {
return null;
}
return
@@ -15,10 +15,13 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author Bas Leijdekkers
*/
public final class JUnit3StyleTestMethodInJUnit4ClassInspection extends BaseInspection {
private static final List<String> IGNORED_FRAMEWORKS = List.of("JUnit4", "JUnit5", "JUnit6");
@Override
protected @NotNull String buildErrorString(Object... infos) {
@@ -49,7 +52,7 @@ public final class JUnit3StyleTestMethodInJUnit4ClassInspection extends BaseInsp
if (testFramework != null) {
if (testFramework.isTestMethod(method, false)) {
final @NonNls String testFrameworkName = testFramework.getName();
if (testFrameworkName.equals("JUnit4") || testFrameworkName.equals("JUnit5")) return;
if (IGNORED_FRAMEWORKS.contains(testFrameworkName)) return;
}
if (AnnotationUtil.isAnnotated(method, "org.junit.Ignore", 0) ||
testFramework.findSetUpMethod(containingClass) == method ||
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit.codeInspection.naming;
import com.intellij.codeInspection.naming.NamingConvention;
@@ -20,7 +20,7 @@ public final class JUnit4MethodNamingConvention extends NamingConvention<PsiMeth
@Override
public boolean isApplicable(PsiMethod member) {
return TestUtils.isExecutableTestMethod(member, List.of("JUnit4", "JUnit5"));
return TestUtils.isExecutableTestMethod(member, List.of("JUnit4", "JUnit5", "JUnit6"));
}
@Override
@@ -291,7 +291,7 @@ public class JUnitConfigurationModel {
if (myProject.isDefault() ||
JUnitUtil.isJUnit5(searchScope, myProject) ||
TestObject.hasJUnit5EnginesAPI(searchScope, JavaPsiFacade.getInstance(myProject))) {
TestObject.hasJupiterEnginesAPI(searchScope, JavaPsiFacade.getInstance(myProject))) {
aModel.addElement(UNIQUE_ID);
aModel.addElement(TAGS);
}
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit5;
import com.intellij.codeInsight.TestFrameworks;
@@ -169,7 +169,7 @@ public class JUnit5AcceptanceTest extends JUnit5CodeInsightTest {
.map(action -> action.getText())
.filter(name -> name.startsWith("Add")).collect(Collectors.toSet());
assertAll("Detected frameworks: " + frameworks,
() -> assertTrue(frameworks.contains("Add 'JUnit5.8.1' to classpath")));
() -> assertTrue(frameworks.contains("Add 'JUnit5' to classpath")));
myFixture.configureByText("MyTest.java",
"class MyTest {@<error descr=\"Cannot resolve symbol 'DisplayName'\">DisplayName</error> void method() {}}");
@@ -179,7 +179,7 @@ public class JUnit5AcceptanceTest extends JUnit5CodeInsightTest {
.map(action -> action.getText())
.filter(name -> name.startsWith("Add")).collect(Collectors.toSet());
assertAll("Detected frameworks: " + displayNameFrameworks,
() -> assertTrue(displayNameFrameworks.contains("Add 'JUnit5.8.1' to classpath")));
() -> assertTrue(displayNameFrameworks.contains("Add 'JUnit5' to classpath")));
}
@Test
+17
View File
@@ -0,0 +1,17 @@
### auto-generated section `build intellij.junit.v6.rt` start
load("@rules_jvm//:jvm.bzl", "jvm_library")
jvm_library(
name = "junit-v6-rt",
module_name = "intellij.junit.v6.rt",
visibility = ["//visibility:public"],
srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True),
kotlinc_opts = "@community//:k17",
deps = [
"//plugins/junit_rt:junit-rt",
"//java/java-runtime:rt",
"@lib//:junit6Launcher-provided",
"@lib//:junit6-provided",
]
)
### auto-generated section `build intellij.junit.v6.rt` end
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_17" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.junit.rt" />
<orderEntry type="module" module-name="intellij.java.rt" />
<orderEntry type="library" scope="PROVIDED" name="JUnit6Launcher" level="project" />
<orderEntry type="library" scope="PROVIDED" name="JUnit6" level="project" />
</component>
</module>
Binary file not shown.
@@ -0,0 +1,159 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.rt.execution.junit.IDEAJUnitListener;
import com.intellij.rt.execution.junit.IDEAJUnitListenerEx;
import com.intellij.rt.junit.IdeaTestRunner;
import org.jspecify.annotations.NonNull;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
import org.junit.platform.launcher.*;
import org.junit.platform.launcher.core.LauncherFactory;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public final class JUnit6IdeaTestRunner implements IdeaTestRunner<TestIdentifier> {
private final List<JUnit6TestExecutionListener> myExecutionListeners = new ArrayList<>();
private ArrayList<String> myListeners;
private Launcher myLauncher;
public JUnit6IdeaTestRunner() {
Runnable warmup = (Runnable) Proxy.newProxyInstance(
JUnit6IdeaTestRunner.class.getClassLoader(),
new Class<?>[]{Runnable.class},
(proxy, method, args) -> null);
warmup.run();
}
@Override
public void createListeners(ArrayList<String> listeners, int count) {
myListeners = listeners;
do {
JUnit6TestExecutionListener currentListener = new JUnit6TestExecutionListener();
myExecutionListeners.add(currentListener);
if (count > 2) {
currentListener.initializeIdSuffix(count);
}
}
while (--count > 0);
myLauncher = LauncherFactory.create();
}
@Override
public int startRunnerWithArgs(String[] args, String programParam, int count, boolean sendTree) {
try {
JUnit6TestExecutionListener listener = myExecutionListeners.get(0);
listener.initializeIdSuffix(!sendTree);
final String[] packageNameRef = new String[1];
final LauncherDiscoveryRequest discoveryRequest = JUnit6TestRunnerUtil.buildRequest(args, packageNameRef, programParam);
List<TestExecutionListener> listeners = new ArrayList<>();
listeners.add(listener);
for (String listenerClassName : myListeners) {
final IDEAJUnitListener junitListener = (IDEAJUnitListener)Class.forName(listenerClassName).getDeclaredConstructor().newInstance();
listeners.add(new MyCustomListenerWrapper(junitListener));
}
if (sendTree) {
for (JUnit6TestExecutionListener executionListener : myExecutionListeners) {
executionListener.setRootName(packageNameRef[0]);
executionListener.setSendTree();
}
}
myLauncher.execute(discoveryRequest, listeners.toArray(new TestExecutionListener[0]));
return listener.wasSuccessful() ? 0 : -1;
}
catch (Exception e) {
System.err.println("Internal Error occurred.");
e.printStackTrace(System.err);
return -2;
}
finally {
if (count > 0) myExecutionListeners.remove(0);
}
}
private TestPlan myForkedTestPlan;
private static final TestIdentifier FAKE_ROOT = TestIdentifier.from(new EngineDescriptor(UniqueId.forEngine("FAKE_ENGINE"), "FAKE ENGINE"));
@Override
public TestIdentifier getTestToStart(String[] args, String name) {
final LauncherDiscoveryRequest discoveryRequest = JUnit6TestRunnerUtil.buildRequest(args, new String[1], "");
myForkedTestPlan = LauncherFactory.create().discover(discoveryRequest);
final Set<TestIdentifier> roots = myForkedTestPlan.getRoots();
if (roots.isEmpty()) return null;
List<TestIdentifier> nonEmptyRoots = roots.stream()
.filter(identifier -> !myForkedTestPlan.getChildren(identifier).isEmpty())
.toList();
if (nonEmptyRoots.isEmpty()) return null;
return nonEmptyRoots.size() == 1 ? nonEmptyRoots.get(0) : FAKE_ROOT;
}
@Override
public List<TestIdentifier> getChildTests(TestIdentifier description) {
if (description == FAKE_ROOT) {
return myForkedTestPlan.getRoots()
.stream()
.flatMap(root -> myForkedTestPlan.getChildren(root).stream())
.collect(Collectors.toList());
}
return new ArrayList<>(myForkedTestPlan.getChildren(description));
}
/**
* {@link com.intellij.execution.junit.TestClass#getForkMode()}
*/
@Override
public String getStartDescription(TestIdentifier child) {
if (myForkedTestPlan.getParent(child).isEmpty()) {
//if fork mode is "repeat", then the only child is the corresponding class
child = myForkedTestPlan.getChildren(child).iterator().next();
}
final TestIdentifier testIdentifier = child;
final String className = JUnit6TestExecutionListener.getClassName(testIdentifier);
final String methodSignature = JUnit6TestExecutionListener.getMethodSignature(testIdentifier);
if (methodSignature != null) {
return className + "," + methodSignature;
}
return className != null ? className : testIdentifier.getDisplayName();
}
@Override
public String getTestClassName(TestIdentifier child) {
return child.toString();
}
private static class MyCustomListenerWrapper implements TestExecutionListener {
private final IDEAJUnitListener myJunitListener;
MyCustomListenerWrapper(IDEAJUnitListener junitListener) {
myJunitListener = junitListener;}
@Override
public void executionStarted(TestIdentifier testIdentifier) {
if (testIdentifier.isTest()) {
final String className = JUnit6TestExecutionListener.getClassName(testIdentifier);
final String methodName = JUnit6TestExecutionListener.getMethodName(testIdentifier);
myJunitListener.testStarted(className, methodName);
}
}
@Override
public void executionFinished(TestIdentifier testIdentifier, @NonNull TestExecutionResult testExecutionResult) {
if (testIdentifier.isTest()) {
final String className = JUnit6TestExecutionListener.getClassName(testIdentifier);
final String methodName = JUnit6TestExecutionListener.getMethodName(testIdentifier);
if (myJunitListener instanceof IDEAJUnitListenerEx) {
((IDEAJUnitListenerEx)myJunitListener).testFinished(className, methodName, testExecutionResult.getStatus() == TestExecutionResult.Status.SUCCESSFUL);
}
else {
myJunitListener.testFinished(className, methodName);
}
}
}
}
}
@@ -0,0 +1,431 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.junit4.ExpectedPatterns;
import com.intellij.rt.execution.junit.ComparisonFailureData;
import com.intellij.rt.execution.junit.MapSerializerUtil;
import org.jspecify.annotations.NonNull;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.descriptor.CompositeTestSource;
import org.junit.platform.engine.support.descriptor.FileSource;
import org.junit.platform.engine.support.descriptor.MethodSource;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.TestPlan;
import org.opentest4j.MultipleFailuresError;
import java.io.File;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import static com.intellij.rt.execution.TestListenerProtocol.CLASS_CONFIGURATION;
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class JUnit6TestExecutionListener implements TestExecutionListener {
private static final String NO_LOCATION_HINT = "";
private static final String NO_LOCATION_HINT_VALUE = "";
private final PrintStream myPrintStream;
private TestPlan myTestPlan;
private long myCurrentTestStart;
private int myFinishCount = 0;
private String myRootName;
private String myPresentableName;
private boolean mySuccessful = true;
private String myIdSuffix = "";
private boolean mySendTree;
public JUnit6TestExecutionListener() {
this(System.out);
}
public JUnit6TestExecutionListener(PrintStream printStream) {
myPrintStream = printStream;
myPrintStream.println("##teamcity[enteredTheMatrix]");
}
public boolean wasSuccessful() {
return mySuccessful;
}
public void initializeIdSuffix(boolean forked) {
if (forked && myIdSuffix.isEmpty()) {
myIdSuffix = String.valueOf(System.currentTimeMillis());
}
}
public void initializeIdSuffix(int i) {
myIdSuffix = i + "th";
}
@Override
public void reportingEntryPublished(@NonNull TestIdentifier testIdentifier, ReportEntry entry) {
StringBuilder builder = new StringBuilder();
builder.append("timestamp = ").append(entry.getTimestamp());
entry.getKeyValuePairs().forEach((key, value) -> builder.append(", ").append(key).append(" = ").append(value));
builder.append("\n");
myPrintStream.println("##teamcity[testStdOut" + idAndName(testIdentifier) + " out = '" + escapeName(builder.toString()) + "']");
}
@Override
public void testPlanExecutionStarted(@NonNull TestPlan testPlan) {
myTestPlan = testPlan;
if (mySendTree) {
for (TestIdentifier root : myTestPlan.getRoots()) {
assert root.isContainer();
for (TestIdentifier testIdentifier : myTestPlan.getChildren(root)) {
String legacyReportingName = testIdentifier.getLegacyReportingName();
if (legacyReportingName.equals(myRootName)) setPresentableName(testIdentifier.getDisplayName());
sendTreeUnderRoot(testIdentifier, new HashSet<>());
}
}
myPrintStream.println("##teamcity[treeEnded]");
}
if (myRootName != null) {
int lastPointIdx = myRootName.lastIndexOf('.');
String name = myRootName;
String comment = null;
if (lastPointIdx >= 0) {
name = myRootName.substring(lastPointIdx + 1);
comment = myRootName.substring(0, lastPointIdx);
}
String messageName = (myPresentableName == null || myPresentableName.isEmpty()) ? name : myPresentableName;
myPrintStream.println("##teamcity[rootName name = '" + escapeName(messageName) +
(comment != null ? ("' comment = '" + escapeName(comment)) : "") + "'" +
" location = 'java:suite://" + escapeName(myRootName) +
"']");
}
}
private void sendTreeUnderRoot(TestIdentifier root, HashSet<TestIdentifier> visited) {
String idAndName = idAndName(root);
if (root.isContainer()) {
boolean skipContainer = shouldSkipContainer(root);
if (!skipContainer) myPrintStream.println("##teamcity[suiteTreeStarted" + idAndName + " " + getLocationHint(root) + "]");
for (TestIdentifier childIdentifier : myTestPlan.getChildren(root)) {
if (visited.add(childIdentifier)) {
sendTreeUnderRoot(childIdentifier, visited);
}
else {
System.err.println("Identifier '" + getId(childIdentifier) + "' is reused");
}
}
if (!skipContainer) myPrintStream.println("##teamcity[suiteTreeEnded" + idAndName + "]");
}
else if (root.isTest()) {
myPrintStream.println("##teamcity[suiteTreeNode " + idAndName + " " + getLocationHint(root) + "]");
}
}
@Override
public void executionSkipped(@NonNull TestIdentifier testIdentifier, @NonNull String reason) {
executionStarted (testIdentifier);
executionFinished(testIdentifier, TestExecutionResult.Status.ABORTED, null, reason);
}
@Override
public void executionStarted(TestIdentifier testIdentifier) {
if (testIdentifier.isTest()) {
testStarted(testIdentifier);
myCurrentTestStart = System.nanoTime();
}
else if (!shouldSkipContainer(testIdentifier)) {
myFinishCount = 0;
myPrintStream.println("##teamcity[testSuiteStarted" + idAndName(testIdentifier) + getLocationHint(testIdentifier) + "]");
}
}
@Override
public void executionFinished(@NonNull TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
TestExecutionResult.Status status = testExecutionResult.getStatus();
Throwable throwableOptional = testExecutionResult.getThrowable().orElse(null);
executionFinished(testIdentifier, status, throwableOptional, null);
mySuccessful &= TestExecutionResult.Status.SUCCESSFUL == testExecutionResult.getStatus();
}
private void executionFinished(TestIdentifier testIdentifier,
TestExecutionResult.Status status,
Throwable throwableOptional,
String reason) {
if (testIdentifier.isTest()) {
long duration = getDuration();
if (status == TestExecutionResult.Status.FAILED) {
testFailure(testIdentifier, MapSerializerUtil.TEST_FAILED, throwableOptional, duration, reason, true);
}
else if (status == TestExecutionResult.Status.ABORTED) {
testFailure(testIdentifier, MapSerializerUtil.TEST_IGNORED, throwableOptional, duration, reason, true);
}
testFinished(testIdentifier, duration);
myFinishCount++;
}
else if (!shouldSkipContainer(testIdentifier) || status == TestExecutionResult.Status.FAILED){
String messageName = null;
if (status == TestExecutionResult.Status.FAILED) {
messageName = MapSerializerUtil.TEST_FAILED;
}
else if (status == TestExecutionResult.Status.ABORTED) {
messageName = MapSerializerUtil.TEST_IGNORED;
}
if (messageName != null) {
Set<TestIdentifier> descendants = myTestPlan != null ? myTestPlan.getDescendants(testIdentifier) : Collections.emptySet();
if (status == TestExecutionResult.Status.FAILED) {
String parentId = getParentId(testIdentifier);
String nameAndId = " name='" + CLASS_CONFIGURATION +
"' nodeId='" + escapeName(getId(testIdentifier)) +
"' parentNodeId='" + escapeName(parentId) + "' ";
myPrintStream.println("##teamcity[testStarted " + nameAndId + " ]");
testFailure(CLASS_CONFIGURATION, getId(testIdentifier), parentId, messageName, throwableOptional, 0, reason, true);
myPrintStream.println("##teamcity[testFinished" + nameAndId + "]");
}
else if (descendants.isEmpty()) {
testFailure(testIdentifier, MapSerializerUtil.TEST_IGNORED, throwableOptional, 0, reason, true);
}
if (!descendants.isEmpty() && myFinishCount == 0) {
for (TestIdentifier childIdentifier : descendants) {
testStarted(childIdentifier);
Throwable throwable = status == TestExecutionResult.Status.ABORTED ? throwableOptional : null;
testFailure(childIdentifier, MapSerializerUtil.TEST_IGNORED, throwable, 0, reason, status == TestExecutionResult.Status.ABORTED);
testFinished(childIdentifier, 0);
}
myFinishCount = 0;
}
}
myPrintStream.println("##teamcity[testSuiteFinished" + idAndName(testIdentifier) + "]");
}
}
private boolean shouldSkipContainer(TestIdentifier testIdentifier) {
UniqueId id = UniqueId.parse(testIdentifier.getUniqueId());
List<UniqueId.Segment> segments = id.getSegments();
if (segments.isEmpty()) return false;
UniqueId.Segment lastSegment = segments.get(segments.size() - 1);
return lastSegment.getType().equals("engine") ||
myRootName != null && myRootName.equals(lastSegment.getValue());
}
protected long getDuration() {
return (System.nanoTime() - myCurrentTestStart) / 1_000_000;
}
private void testStarted(TestIdentifier testIdentifier) {
myPrintStream.println("##teamcity[testStarted" + idAndName(testIdentifier) + " " + getLocationHint(testIdentifier) + "]");
}
private void testFinished(TestIdentifier testIdentifier, long duration) {
myPrintStream.println("##teamcity[testFinished" + idAndName(testIdentifier) + (duration > 0 ? " duration='" + duration + "'" : "") + "]");
}
private void testFailure(TestIdentifier testIdentifier,
String messageName,
Throwable ex,
long duration,
String reason,
boolean includeThrowable) {
testFailure(testIdentifier.getDisplayName(), getId(testIdentifier), getParentId(testIdentifier), messageName, ex, duration, reason, includeThrowable);
}
private void testFailure(String methodName,
String id,
String parentId,
String messageName,
Throwable ex,
long duration,
String reason,
boolean includeThrowable) {
Map<String, String> attrs = new LinkedHashMap<>();
attrs.put("name", methodName);
attrs.put("id", id);
attrs.put("nodeId", id);
attrs.put("parentNodeId", parentId);
if (duration > 0) {
attrs.put("duration", Long.toString(duration));
}
if (reason != null) {
attrs.put("message", reason);
}
try {
if (ex != null) {
ComparisonFailureData failureData = null;
if (ex instanceof MultipleFailuresError && ((MultipleFailuresError)ex).hasFailures()) {
for (Throwable assertionError : ((MultipleFailuresError)ex).getFailures()) {
testFailure(methodName, id, parentId, messageName, assertionError, duration, reason, true);
}
}
else {
try {
failureData = ExpectedPatterns.createExceptionNotification(ex);
}
catch (Throwable ignore) {}
}
if (includeThrowable || failureData == null) {
ComparisonFailureData.registerSMAttributes(failureData, getTrace(ex), ex.getMessage(), attrs, ex, "Comparison Failure: ", "expected: <");
}
else {
ComparisonFailureData.registerSMAttributes(failureData, "", ex.getMessage(), attrs, ex, "Comparison Failure: ", "expected: <");
}
}
}
finally {
myPrintStream.println(MapSerializerUtil.asString(messageName, attrs));
}
}
protected String getTrace(Throwable ex) {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
ex.printStackTrace(writer);
return stringWriter.toString();
}
public void setRootName(String rootName) {
myRootName = rootName;
}
public void setPresentableName(String presentableName) {
myPresentableName = presentableName;
}
public void setSendTree() {
mySendTree = true;
}
private String getId(TestIdentifier identifier) {
return identifier.getUniqueId() + myIdSuffix;
}
private String idAndName(TestIdentifier testIdentifier) {
return " id='" + escapeName(getId(testIdentifier)) +
"' name='" + escapeName(testIdentifier.getDisplayName()) +
"' nodeId='" + escapeName(getId(testIdentifier)) +
"' parentNodeId='" + escapeName(getParentId(testIdentifier)) + "'";
}
private String getParentId(TestIdentifier testIdentifier) {
Optional<TestIdentifier> parent = myTestPlan.getParent(testIdentifier);
return parent
.map(identifier -> shouldSkipContainer(identifier) ? getParentId(identifier) : identifier.getUniqueId() + myIdSuffix)
.orElse("0");
}
private String getLocationHint(TestIdentifier root) {
return getLocationHint(root, myTestPlan.getParent(root).orElse(null));
}
public static String getLocationHint(TestIdentifier root, TestIdentifier rootParent) {
return root.getSource()
.map(testSource -> getLocationHintValue(testSource, rootParent != null ? rootParent.getSource().orElse(null) : null))
.filter(maybeLocationHintValue -> !NO_LOCATION_HINT_VALUE.equals(maybeLocationHintValue))
.map(locationHintValue -> "locationHint='" + locationHintValue + "'" + getMetaInfo(root))
.orElse(NO_LOCATION_HINT);
}
private static String getMetaInfo(TestIdentifier root) {
return root.getSource()
.map(testSource -> {
if (testSource instanceof MethodSource) {
return " metainfo='" + ((MethodSource)testSource).getMethodParameterTypes() + "'";
}
if (testSource instanceof ClassSource) {
return ((ClassSource)testSource).getPosition()
.map(position ->
" metainfo='"
// Convert JUnit's 1-based values to 0-based
+ (position.getLine() - 1) + ":"
+ (position.getColumn().orElse(1) - 1) + "'")
.orElse(NO_LOCATION_HINT);
}
return NO_LOCATION_HINT;
})
.orElse(NO_LOCATION_HINT);
}
public static String getLocationHintValue(TestSource testSource, TestSource parentSource) {
if (testSource instanceof CompositeTestSource compositeTestSource) {
for (TestSource sourceFromComposite : compositeTestSource.getSources()) {
String locationHintValue = getLocationHintValue(sourceFromComposite, parentSource);
if (!NO_LOCATION_HINT_VALUE.equals(locationHintValue)) {
return locationHintValue;
}
}
return NO_LOCATION_HINT_VALUE;
}
if (testSource instanceof FileSource fileSource) {
File file = fileSource.getFile();
String line = fileSource.getPosition()
.map(position -> ":" + position.getLine())
.orElse("");
return "file://" + file.getAbsolutePath() + line;
}
if (testSource instanceof MethodSource methodSource) {
return javaLocation(methodSource.getClassName(), methodSource.getMethodName(), true);
}
if (testSource instanceof ClassSource) {
String className = ((ClassSource)testSource).getClassName();
return javaLocation(className, null, false);
}
if (parentSource != null) {
return getLocationHintValue(parentSource,null);
}
return NO_LOCATION_HINT_VALUE;
}
private static String javaLocation(String className, String maybeMethodName, boolean isTest) {
String type = isTest ? "test" : "suite";
String methodName = maybeMethodName == null ? "" : "/" + maybeMethodName;
String location = escapeName(className + methodName);
return "java:" + type + "://" + location;
}
private static String escapeName(String str) {
return MapSerializerUtil.escapeStr(str, MapSerializerUtil.STD_ESCAPER);
}
public static String getClassName(TestIdentifier description) {
return description.getSource().map(source -> {
if (source instanceof MethodSource) {
return ((MethodSource)source).getClassName();
}
if (source instanceof ClassSource) {
return ((ClassSource)source).getClassName();
}
return null;
}).orElse(null);
}
public static String getMethodName(TestIdentifier testIdentifier) {
return testIdentifier.getSource().map((source) -> {
if (source instanceof MethodSource) {
return ((MethodSource)source).getMethodName();
}
return null;
}).orElse(null);
}
static String getMethodSignature(TestIdentifier testIdentifier) {
return testIdentifier.getSource().map((source) -> {
if (source instanceof MethodSource) {
String parameterTypes = ((MethodSource)source).getMethodParameterTypes();
return ((MethodSource)source).getMethodName() + (parameterTypes != null ? "(" + parameterTypes + ")" : "");
}
return null;
}).orElse(null);
}
}
@@ -0,0 +1,293 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import org.jspecify.annotations.NonNull;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.discovery.*;
import org.junit.platform.engine.support.descriptor.MethodSource;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.PostDiscoveryFilter;
import org.junit.platform.launcher.TagFilter;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class JUnit6TestRunnerUtil {
private static final Pattern VALUE_SOURCE_PATTERN = Pattern.compile("valueSource\\s(\\d+)");
private static Class<?> NESTED_CLASS_SELECTOR_CLASS = null;
public static LauncherDiscoveryRequest buildRequest(String[] suiteClassNames, String[] packageNameRef, String param) {
if (suiteClassNames.length == 0) {
return null;
}
LauncherDiscoveryRequestBuilder builder = LauncherDiscoveryRequestBuilder.request();
if (suiteClassNames.length == 1 && suiteClassNames[0].charAt(0) == '@') {
// all tests in the package specified
try (BufferedReader reader = new BufferedReader(new FileReader(suiteClassNames[0].substring(1), StandardCharsets.UTF_8))) {
final String packageName = reader.readLine();
if (packageName == null) return null;
String tags = reader.readLine();
String filters = reader.readLine();
String line;
List<DiscoverySelector> selectors = new ArrayList<>();
while ((line = reader.readLine()) != null) {
DiscoverySelector selector = createSelector(line, null);
if (selector != null) {
selectors.add(selector);
}
}
if (hasBrokenSelector(selectors)) {
builder.filters(createMethodFilter(new ArrayList<>(selectors)));
for (int i = 0; i < selectors.size(); i++) {
DiscoverySelector selector = selectors.get(i);
if (selector instanceof MethodSelector) {
selectors.set(i, createClassSelector(((MethodSelector)selector).getClassName()));
}
}
}
packageNameRef[0] = packageName.isEmpty() ? "<default package>" : packageName;
if (selectors.isEmpty()) {
builder.selectors(DiscoverySelectors.selectPackage(packageName));
}
else {
builder.selectors(selectors);
if (!packageName.isEmpty()) {
builder.filters(PackageNameFilter.includePackageNames(packageName));
}
}
if (filters != null && !filters.isEmpty()) {
String[] classNames = filters.split("\\|\\|");
for (String className : classNames) {
if (!className.contains("*")) {
try {
Class.forName(className, false, JUnit6TestRunnerUtil.class.getClassLoader());
}
catch (ClassNotFoundException e) {
System.err.println(MessageFormat.format(ResourceBundle.getBundle("messages.RuntimeBundle").getString("junit.class.not.found"), className));
}
}
}
builder.filters(ClassNameFilter.includeClassNamePatterns(classNames));
}
if (tags != null && !tags.isEmpty()) {
builder.filters(TagFilter.includeTags(tags.split(" ")));
}
return builder.filters(ClassNameFilter.excludeClassNamePatterns("com\\.intellij\\.rt.*", "com\\.intellij\\.junit3.*")).build();
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
else {
DiscoverySelector selector = createSelector(suiteClassNames[0], packageNameRef);
if (selector instanceof MethodSelector) {
DiscoverySelector classSelector = createClassSelector(((MethodSelector)selector).getClassName());
DiscoverySelector methodSelector = isNestedClassSelector(classSelector)
? DiscoverySelectors.selectMethod(((NestedClassSelector)classSelector).getNestedClassName(),
((MethodSelector)selector).getMethodName())
: selector;
builder.filters(createMethodFilter(Collections.singletonList(methodSelector)));
selector = classSelector;
}
if (selector instanceof MethodSelector && param != null) {
DiscoverySelector methodSelectIteration = createMethodSelectIteration(selector, param);
if (methodSelectIteration != null) {
return builder.selectors(methodSelectIteration).build();
}
}
assert selector != null : "selector by class name is never null";
return builder.selectors(selector).build();
}
return null;
}
private static boolean isNestedClassSelector(DiscoverySelector selector) {
if (NESTED_CLASS_SELECTOR_CLASS == null) {
try {
NESTED_CLASS_SELECTOR_CLASS = Class.forName("org.junit.platform.engine.discovery.NestedClassSelector");
}
catch (ClassNotFoundException e) {
return false;
}
}
return NESTED_CLASS_SELECTOR_CLASS.isInstance(selector);
}
private static boolean loadMethodByReflection(MethodSelector selector) {
try {
Class<?> aClass = Class.forName(selector.getClassName());
return ReflectionSupport.findMethod(aClass, selector.getMethodName(), selector.getParameterTypes()).isPresent();
}
catch (ClassNotFoundException e) {
return false;
}
}
private static boolean hasBrokenSelector(List<DiscoverySelector> selectors) {
for (DiscoverySelector selector : selectors) {
if (selector instanceof MethodSelector && !loadMethodByReflection((MethodSelector)selector)) {
return true;
}
}
return false;
}
private static PostDiscoveryFilter createMethodFilter(List<DiscoverySelector> selectors) {
return new PostDiscoveryFilter() {
@Override
public @NonNull FilterResult apply(TestDescriptor descriptor) {
return FilterResult.includedIf(shouldRun(descriptor),
() -> descriptor.getDisplayName() + " matches",
() -> descriptor.getDisplayName() + " doesn't match");
}
private boolean shouldRun(TestDescriptor descriptor) {
TestSource source = descriptor.getSource().orElse(null);
if (source instanceof MethodSource) {
for (DiscoverySelector selector : selectors) {
if (selector instanceof MethodSelector &&
((MethodSelector)selector).getMethodName().equals(((MethodSource)source).getMethodName()) &&
(((MethodSelector)selector).getClassName().equals(((MethodSource)source).getClassName()) ||
inNestedClass((MethodSource)source, createClassSelector(((MethodSelector)selector).getClassName())))) {
return true;
}
}
for (DiscoverySelector selector : selectors) {
if (selector instanceof ClassSelector && ((ClassSelector)selector).getClassName().equals(((MethodSource)source).getClassName()) ||
inNestedClass((MethodSource)source, selector)) {
return true;
}
}
return false;
}
return true;
}
private static boolean inNestedClass(MethodSource source, DiscoverySelector selector) {
return isNestedClassSelector(selector) &&
((NestedClassSelector)selector).getNestedClassName().equals(source.getClassName());
}
};
}
/**
* Unique id is prepended with prefix: @see com.intellij.execution.junit.TestUniqueId#getUniqueIdPresentation()
* Method contains ','
*/
private static DiscoverySelector createSelector(String line, String[] packageNameRef) {
if (line.startsWith("\u001B")) {
String uniqueId = line.substring("\u001B".length());
return DiscoverySelectors.selectUniqueId(uniqueId);
}
else if (line.startsWith("\u002B")) {
String directory = line.substring("\u002B".length());
List<ClasspathRootSelector> selectors = DiscoverySelectors.selectClasspathRoots(Collections.singleton(Paths.get(directory)));
if (selectors.isEmpty()) {
return null;
} else {
return selectors.iterator().next();
}
}
else if (line.contains(",")) {
MethodSelector selector = DiscoverySelectors.selectMethod(line.replaceFirst(",", "#"));
if (packageNameRef != null) {
packageNameRef[0] = selector.getClassName();
}
return selector;
}
else {
if (packageNameRef != null) {
packageNameRef[0] = line;
}
return createClassSelector(line);
}
}
private static DiscoverySelector createClassSelector(String line) {
int nestedClassIdx = line.lastIndexOf("$");
if (nestedClassIdx > 0) {
AtomicReference<DiscoverySelector> nestedClassSelector = new AtomicReference<>();
ReflectionSupport.tryToLoadClass(line).ifFailure(__ -> {
nestedClassSelector.set(getNestedSelector(line, nestedClassIdx));
});
if (nestedClassSelector.get() != null) return nestedClassSelector.get();
}
return DiscoverySelectors.selectClass(line);
}
private static DiscoverySelector createMethodSelectIteration(DiscoverySelector methodSelector, String param) {
Integer index = null;
if (param != null) {
Matcher matcher = VALUE_SOURCE_PATTERN.matcher(param);
if (matcher.find()) {
String group = matcher.group(1);
try {
index = Integer.parseInt(group);
}
catch (NumberFormatException ignored) {
}
}
}
if (index != null) {
try {
return DiscoverySelectors.selectIteration(methodSelector, index);
}
catch (NoSuchMethodError e) {
return null;
}
}
return null;
}
private static NestedClassSelector getNestedSelector(String line, int nestedClassIdx) {
String enclosingClass = line.substring(0, nestedClassIdx);
String nestedClassName = line.substring(nestedClassIdx + 1);
DiscoverySelector enclosingClassSelector = createClassSelector(enclosingClass);
Class<?> klass = isNestedClassSelector(enclosingClassSelector)
? ((NestedClassSelector)enclosingClassSelector).getNestedClass()
: ((ClassSelector)enclosingClassSelector).getJavaClass();
Class<?> superclass = klass.getSuperclass();
while (superclass != null) {
for (Class<?> nested : superclass.getDeclaredClasses()) {
if (nested.getSimpleName().equals(nestedClassName)) {
List<Class<?>> enclosingClasses;
if (isNestedClassSelector(enclosingClassSelector)) {
enclosingClasses = new ArrayList<>(((NestedClassSelector)enclosingClassSelector).getEnclosingClasses());
enclosingClasses.add(klass);
}
else {
enclosingClasses = Collections.singletonList(klass);
}
return DiscoverySelectors.selectNestedClass(enclosingClasses, nested);
}
}
superclass = superclass.getSuperclass();
}
return null;
}
}
+45
View File
@@ -0,0 +1,45 @@
### auto-generated section `build intellij.junit.v6.rt.tests` start
load("@rules_jvm//:jvm.bzl", "jvm_library")
jvm_library(
name = "junit-v6-rt-tests_test_lib",
module_name = "intellij.junit.v6.rt.tests",
visibility = ["//visibility:public"],
srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True),
deps = [
"//java/testFramework",
"//java/execution/impl",
"//plugins/junit",
"//java/java-tests:tests_test_lib",
"//aether-dependency-resolver",
"//aether-dependency-resolver:aether-dependency-resolver_test_lib",
"//java/compiler/openapi:java-compiler",
"//platform/analysis-api:analysis",
"//platform/core-api:core",
"//platform/core-ui",
"//platform/editor-ui-api:editor-ui",
"//platform/execution-impl",
"//jps/model-api:model",
"//platform/lang-core",
"//platform/projectModel-api:projectModel",
"//platform/smRunner",
"//platform/smRunner:smRunner_test_lib",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
"//java/java-runtime:rt",
"@lib//:stream_ex",
"//platform/platform-util-io:ide-util-io",
"//platform/testFramework/junit5",
"//platform/testFramework/junit5:junit5_test_lib",
]
)
### auto-generated section `build intellij.junit.v6.rt.tests` end
### auto-generated section `test intellij.junit.v6.rt.tests` start
load("@community//build:tests-options.bzl", "jps_test")
jps_test(
name = "junit-v6-rt-tests_test",
runtime_deps = [":junit-v6-rt-tests_test_lib"]
)
### auto-generated section `test intellij.junit.v6.rt.tests` end
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.execution.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.junit" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.aetherDependencyResolver" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.compiler" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.analysis" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core.ui" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.editor.ui" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.execution.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.jps.model" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.lang.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.projectModel" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.smRunner" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.rt" scope="TEST" />
<orderEntry type="library" scope="TEST" name="StreamEx" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
</component>
</module>
@@ -0,0 +1,188 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.junit.*;
import com.intellij.execution.junit2.info.MethodLocation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.testIntegration.TestFramework;
import one.util.streamex.StreamEx;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
public class JUnit6AcceptanceTest extends JUnit6CodeInsightTest {
@Test
void testFactoryMethods() {
PsiClass aClass = myFixture.addClass(
"/** @noinspection ALL*/ class MyTest {@org.junit.jupiter.api.TestFactory java.util.List<org.junit.jupiter.api.DynamicTest> tests() {return null;}}");
PsiMethod factoryMethod = aClass.getMethods()[0];
assertNotNull(factoryMethod);
assertTrue(JUnitUtil.isTestAnnotated(factoryMethod));
}
@Test
void testDefaultMethodInInterface() {
PsiClass aClass = myFixture.addClass("interface MyTest {@org.junit.jupiter.api.Test default void method() {}}");
assertTrue(JUnitUtil.isTestClass(aClass, false, false));
}
@Test
void recognizedInnerClassesWithTestMethods() {
PsiClass aClass = myFixture
.addClass("import org.junit.jupiter.api.*; /** @noinspection ALL*/ class MyTest {@Nested class NTest { @Test void method() {}}}");
assertTrue(JUnitUtil.isTestClass(aClass, false, false));
PsiClass innerClass = aClass.getInnerClasses()[0];
assertTrue(JUnitUtil.isTestClass(innerClass));
assertTrue(JUnitUtil.isTestMethod(MethodLocation.elementInClass(innerClass.getMethods()[0], innerClass)));
}
@Test
void rejectStaticMethods() {
PsiClass aClass =
myFixture.addClass("import org.junit.jupiter.api.*; /** @noinspection ALL*/ class MyTest { @Test static void method() {}}");
assertFalse(JUnitUtil.isTestClass(aClass, false, false));
assertFalse(JUnitUtil.isTestMethod(MethodLocation.elementInClass(aClass.getMethods()[0], aClass)));
}
@Test
void rejectPrivateMethods() {
PsiClass aClass =
myFixture.addClass("import org.junit.jupiter.api.*; /** @noinspection ALL*/ class MyTest { @Test private void method() {}}");
assertFalse(JUnitUtil.isTestClass(aClass, false, false));
assertFalse(JUnitUtil.isTestMethod(MethodLocation.elementInClass(aClass.getMethods()[0], aClass)));
}
@Test
void testFrameworkDetection() {
PsiClass aClass = myFixture.addClass("/** @noinspection ALL*/ class MyTest {@org.junit.jupiter.api.Test void method() {}}");
assertNotNull(aClass);
TestFramework framework = TestFrameworks.detectFramework(aClass);
assertNotNull(framework, "No test framework detected");
assertInstanceOf(JUnit6Framework.class, framework, framework.getName());
}
@Test
void testFrameworkDetectionWithMixedJunit4Junit6() throws ExecutionException {
myFixture.addClass("package org.junit; public @interface Test {}");
PsiClass aClass = myFixture.addClass("/** @noinspection ALL*/ public class MyTest {@org.junit.jupiter.api.Test void method() {} @org.junit.Test void method2(){} }");
assertNotNull(aClass);
TestFramework framework = TestFrameworks.detectFramework(aClass);
assertInstanceOf(JUnit4Framework.class, framework, framework.getName());
JUnitConfiguration configuration = new JUnitConfiguration("", myFixture.getProject());
configuration.beClassConfiguration(aClass);
JavaParameters parameters = configuration.getTestObject().createJavaParameters4Tests();
assertTrue(parameters.getProgramParametersList().hasParameter("-junit6"));
}
@Test
void testClassWithDisabledCondition() throws ExecutionException {
myFixture.addClass("package org.junit.jupiter.api; public @interface Disabled {}");
PsiClass aClass = myFixture.addClass(
"""
/** @noinspection ALL*/
@org.junit.jupiter.api.Disabled
public class MyTest {@org.junit.jupiter.api.Test void method() {}}""");
assertNotNull(aClass);
TestFramework framework = TestFrameworks.detectFramework(aClass);
assertInstanceOf(JUnit6Framework.class, framework, framework.getName());
JUnitConfiguration configuration = new JUnitConfiguration("", myFixture.getProject());
configuration.beClassConfiguration(aClass);
JavaParameters parameters = configuration.getTestObject().createJavaParameters4Tests();
assertTrue(parameters.getVMParametersList().hasParameter("-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition"));
}
@Test
void testMetaDisabledClass() throws ExecutionException {
myFixture.addClass("""
package org.test.sample;
@org.junit.jupiter.api.Disabled
public @interface MetaDisabled {}""");
PsiClass aClass = myFixture.addClass(
"""
/** @noinspection ALL*/
@org.test.sample.MetaDisabled
public class MyTest {@org.junit.jupiter.api.Test void method() {}}""");
assertNotNull(aClass);
TestFramework framework = TestFrameworks.detectFramework(aClass);
assertInstanceOf(JUnit6Framework.class, framework, framework.getName());
JUnitConfiguration configuration = new JUnitConfiguration("", myFixture.getProject());
configuration.beClassConfiguration(aClass);
JavaParameters parameters = configuration.getTestObject().createJavaParameters4Tests();
assertTrue(parameters.getVMParametersList().hasParameter("-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition"));
}
@Test
void testWithDisabledCondition() throws ExecutionException {
PsiClass aClass = myFixture.addClass(
"""
/** @noinspection ALL*/
public class MyTest {
@org.junit.jupiter.api.Disabled
@org.junit.jupiter.api.Test void method() {}}""");
assertNotNull(aClass);
TestFramework framework = TestFrameworks.detectFramework(aClass);
assertInstanceOf(JUnit6Framework.class, framework, framework.getName());
JUnitConfiguration configuration = new JUnitConfiguration("", myFixture.getProject());
@SuppressWarnings("OptionalGetWithoutIsPresent") PsiMethod method = Arrays.stream(aClass.getMethods()).findFirst().get();
configuration.beMethodConfiguration(MethodLocation.elementInClass(method, aClass));
JavaParameters parameters = configuration.getTestObject().createJavaParameters4Tests();
assertTrue(parameters.getVMParametersList().hasParameter("-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition"));
}
@Test
void methodPresentations() {
myFixture.addClass("package a; public class TestInfo {}");
PsiClass aClass = myFixture.addClass("/** @noinspection ALL*/ class MyTest<T extends a.TestInfo> {" +
" @org.junit.jupiter.api.Test void method() {}" +
" @org.junit.jupiter.api.Test void method(a.TestInfo info) {}" +
" @org.junit.jupiter.api.Test void method(T info) {}" +
" @org.junit.Test void method1() {}" +
" @org.junit.Test void method1(a.TestInfo info) {}" +
"}");
assertNotNull(aClass);
Stream<String> expectedData = Arrays.stream(new String[]{"method", "method(a.TestInfo)", "method(a.TestInfo)", "method1", "method1"});
StreamEx.of(aClass.getMethods())
.zipWith(expectedData)
.forEach(e -> assertEquals(e.getValue(), JUnitConfiguration.Data.getMethodPresentation(e.getKey())));
}
@Test
void metaAnnotations() {
myFixture.addClass("""
package a;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@org.junit.jupiter.api.Test
@interface MyTest {}""");
PsiClass aClass = myFixture.addClass("""
class ATest {
@a.MyTest
void foo() {}
}
""");
assertTrue(JUnitUtil.isTestClass(aClass, false, false));
assertTrue(JUnitUtil.isTestMethod(MethodLocation.elementInClass(aClass.getMethods()[0], aClass)));
}
@Test
void customEngineOnly() {
PsiClass customEngineTest = myFixture.addClass("import org.junit.platform.commons.annotation.Testable;" +
" /** @noinspection ALL*/ " +
"@Testable\n" +
"class MyTests{}");
assertTrue(JUnitUtil.isTestClass(customEngineTest));
PsiClass customEngineAnnotationOnSuper = myFixture.addClass("class MyCustomClass extends MyTests{}");
assertTrue(JUnitUtil.isTestClass(customEngineAnnotationOnSuper));
}
}
@@ -0,0 +1,38 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.fixtures.*;
import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
import com.intellij.testFramework.junit5.RunInEdt;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@RunInEdt(writeIntent = true)
public abstract class JUnit6CodeInsightTest {
protected JavaCodeInsightTestFixture myFixture;
@BeforeEach
void setUp() throws Exception {
IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder = factory.createLightFixtureBuilder(
new DefaultLightProjectDescriptor(IdeaTestUtil::getMockJdk21), "JUnit6CodeInsightTest"
);
final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(fixture, new LightTempDirTestFixtureImpl(true));
myFixture.setUp();
//init junit 6 framework
myFixture.addClass("package org.junit.platform.commons.annotation; public @interface Testable {}");
myFixture.addClass("package org.junit.jupiter.api; public interface MethodOrderer { class Default implements MethodOrderer {} }");
myFixture.addClass("package org.junit.jupiter.api; @org.junit.platform.commons.annotation.Testable public @interface Test {}");
myFixture.addClass("package org.junit.jupiter.api; public @interface Nested {}");
myFixture.addClass("package org.junit.jupiter.api; @org.junit.platform.commons.annotation.Testable public @interface TestFactory {}");
myFixture.addClass("package org.junit.jupiter.params.provider; public @interface MethodSource {}");
}
@AfterEach
void tearDown() throws Exception {
myFixture.tearDown();
}
}
@@ -0,0 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
public interface JUnit6Constants {
String VERSION = "6.0.0-RC2";
}
@@ -0,0 +1,165 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.java.execution.AbstractTestFrameworkCompilingIntegrationTest;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.PlatformTestUtil;
import jetbrains.buildServer.messages.serviceMessages.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.intellij.junit6.ServiceMessageUtil.replaceAttributes;
@SuppressWarnings("SSBasedInspection")
public class JUnit6EventsTest extends AbstractTestFrameworkCompilingIntegrationTest {
@Override
protected String getTestContentRoot() {
return VfsUtilCore.pathToUrl(PlatformTestUtil.getCommunityPath() + "/plugins/junit6_rt_tests/testData/integration/events");
}
@Override
protected void setupModule() throws Exception {
super.setupModule();
ModuleRootModificationUtil.updateModel(myModule, model -> model.addContentEntry(getTestContentRoot())
.addSourceFolder(getTestContentRoot() + "/test", true));
final ArtifactRepositoryManager repoManager = getRepoManager();
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION),
repoManager);
}
public void testMultipleFailures() throws Exception {
ProcessOutput output = doStartTestsProcess(createRunMethodConfiguration("com.intellij.junit6.testData.MyTestClass", "test1"));
assertEmpty(output.err);
List<ServiceMessage> messages = output.messages;
Map<String, TestStarted> tests = getStartedTests(messages);
// Ensure our test method started with a proper location hint
TestStarted started = tests.values().stream()
.filter(t -> "java:test://com.intellij.junit6.testData.MyTestClass/test1".equals(t.getAttributes().get("locationHint")))
.findFirst().orElse(null);
assertNotNull(started);
assertEquals("java:test://com.intellij.junit6.testData.MyTestClass/test1", started.getAttributes().get("locationHint"));
String nodeId = started.getAttributes().get("nodeId");
// There should be multiple failures reported for the same test
List<ServiceMessage> failed = messages.stream()
.filter(m -> nodeId.equals(m.getAttributes().get("nodeId")))
.filter(m -> m instanceof TestFailed).toList();
assertSize(3, failed);
Set<String> errors = failed.stream().filter(m -> m.getAttributes().get("expected") != null)
.map(m -> "expected='" + m.getAttributes().get("expected") + "', actual='" + m.getAttributes().get("actual") + "'")
.collect(Collectors.toSet());
assertEquals(Set.of(
"expected='expected1', actual='actual1'",
"expected='expected2', actual='actual2'"
), errors);
// StdOut message should include our published entries
Set<String> outs = messages.stream()
.filter(m -> nodeId.equals(m.getAttributes().get("nodeId")))
.filter(m -> m instanceof TestStdOut)
.map(m -> m.getAttributes().get("out"))
.map(s -> s.replaceAll("timestamp = [0-9\\-:.T]+", "timestamp = ##timestamp##"))
.collect(Collectors.toSet());
assertEquals(Set.of("timestamp = ##timestamp##, key1 = value1, stdout = out1\n"), outs);
}
public void testContainerFailure() throws Exception {
ProcessOutput output = doStartTestsProcess(createRunMethodConfiguration("com.intellij.junit6.testData.MyTestClass", "brokenStream"));
assertEmpty(output.err);
// Expect a configuration failure
List<String> test = output.messages.stream().filter(BaseTestMessage.class::isInstance)
.map(BaseTestMessage.class::cast)
.filter(m -> m.getTestName().equals("Class Configuration"))
.map(m -> replaceAttributes(m, Map.of("details", "##details##")))
.toList();
assertEquals(List.of(
"##teamcity[testStarted name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']",
"##teamcity[testFailed name='Class Configuration' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' error='true' message='java.lang.IllegalStateException: broken' details='##details##']",
"##teamcity[testFinished name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']"
), test);
}
public void testContainerDisabled() throws Exception {
ProcessOutput output = doStartTestsProcess(createRunClassConfiguration("com.intellij.junit6.testData.MyTestClass"));
assertEmpty(output.err);
List<String> tests = output.messages.stream().filter(m -> m instanceof BaseTestMessage)
.map(m -> replaceAttributes(m, Map.of(
"timestamp", "##timestamp##",
"duration", "##duration##",
"message", "##message##",
"details", "##details##"
)))
.map(s -> s.replaceAll("timestamp = [0-9\\-:.T]+", "timestamp = ##timestamp##")).toList();
assertEquals(List.of(
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/disabledTest' metainfo='']",
"##teamcity[testIgnored name='disabledTest()' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0' message='##message##']",
"##teamcity[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0']",
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/test1' metainfo='org.junit.jupiter.api.TestReporter']",
"##teamcity[testStdOut id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' out='timestamp = ##timestamp##, key1 = value1, stdout = out1|n']",
"##teamcity[testFailed name='test1(TestReporter)' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' expected='expected1' actual='actual1' details='##details##']",
"##teamcity[testFailed name='test1(TestReporter)' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' expected='expected2' actual='actual2' details='##details##']",
"##teamcity[testFailed name='test1(TestReporter)' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' details='##details##']",
"##teamcity[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##']",
"##teamcity[testIgnored name='brokenStreamDisabled()' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStreamDisabled()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStreamDisabled()|]' parentNodeId='0' message='##message##']",
"##teamcity[testStarted name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']",
"##teamcity[testFailed name='Class Configuration' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' error='true' message='##message##' details='##details##']",
"##teamcity[testFinished name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']"
), tests);
}
public void testEscaping() throws Exception {
ProcessOutput output = doStartTestsProcess(createRunMethodConfiguration("com.intellij.junit6.testData.AnnotationsTestClass", "test1"));
assertEmpty(output.err);
Map<String, TestStarted> tests = getStartedTests(output.messages);
TestStarted started = tests.values().stream()
.filter(t -> t.getAttributes().get("locationHint").equals("java:test://com.intellij.junit6.testData.AnnotationsTestClass/test1"))
.findFirst().orElse(null);
assertNotNull(started);
// TeamCity service messages encode names internally; here we ensure the original display name is preserved
assertEquals("test's method", started.getAttributes().get("name"));
}
@NotNull
private RunConfiguration createRunMethodConfiguration(final String className, final String methodName) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(className, GlobalSearchScope.projectScope(myProject));
assertNotNull(aClass);
PsiMethod method = aClass.findMethodsByName(methodName, false)[0];
RunConfiguration configuration = createConfiguration(method);
assertInstanceOf(configuration, JUnitConfiguration.class);
return configuration;
}
@NotNull
private RunConfiguration createRunClassConfiguration(final String className) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(className, GlobalSearchScope.projectScope(myProject));
assertNotNull(aClass);
RunConfiguration configuration = createConfiguration(aClass);
assertInstanceOf(configuration, JUnitConfiguration.class);
return configuration;
}
private static Map<String, TestStarted> getStartedTests(List<ServiceMessage> messages) {
return messages.stream().filter(TestStarted.class::isInstance).map(TestStarted.class::cast)
.collect(Collectors.toMap(t -> t.getAttributes().get("id"), t -> t));
}
}
@@ -0,0 +1,44 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.testIntegration.BaseGenerateTestSupportMethodAction;
import com.intellij.testIntegration.TestIntegrationUtils;
import org.junit.jupiter.api.Test;
public class JUnit6GenerationTest extends JUnit6CodeInsightTest {
@Test
void testMethodInTopLevelClass() {
doTest("import org.junit.jupiter.api.Test; class MyTest {<caret> @Test void m2(){}}",
"""
import org.junit.jupiter.api.Test; class MyTest {
@Test
void name() {
\s
}
@Test void m2(){}}""");
}
@Test
void testMethodInNestedClass() {
doTest("import org.junit.jupiter.api.Nested; class MyTest { @Nested class NTest { <caret>}}",
"""
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class MyTest { @Nested class NTest {
@Test
void name() {
\s
}
}}""");
}
private void doTest(String text, String expected) {
myFixture.configureByText("MyTest.java", text);
new BaseGenerateTestSupportMethodAction.MyHandler(TestIntegrationUtils.MethodKind.TEST)
.invoke(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile());
myFixture.checkResult(expected);
}
}
@@ -0,0 +1,52 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.java.execution.AbstractTestFrameworkCompilingIntegrationTest;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.PlatformTestUtil;
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
import org.junit.jupiter.api.Assertions;
import java.util.Arrays;
public class JUnit6NamingTest extends AbstractTestFrameworkCompilingIntegrationTest {
@Override
protected String getTestContentRoot() {
return VfsUtilCore.pathToUrl(PlatformTestUtil.getCommunityPath() + "/plugins/junit6_rt_tests/testData/integration/naming");
}
@Override
protected void setupModule() throws Exception {
super.setupModule();
ModuleRootModificationUtil.updateModel(myModule, model -> model.addContentEntry(getTestContentRoot())
.addSourceFolder(getTestContentRoot() + "/test", true));
final ArtifactRepositoryManager repoManager = getRepoManager();
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION), repoManager);
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.platform", "junit-platform-suite-api", JUnit6Constants.VERSION), repoManager);
}
public void testArrayParameters() {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass("MyTest", GlobalSearchScope.projectScope(myProject));
assertNotNull(aClass);
PsiJavaFile file = (PsiJavaFile)aClass.getContainingFile();
String[] methodPresentations =
Arrays.stream(file.getClasses()[0].getMethods())
.map(method -> JUnitConfiguration.Data.getMethodPresentation(method))
.toArray(String[]::new);
Assertions.assertArrayEquals(new String[]{"foo(int[])",
"foo(int)",
"foo([Ljava.lang.String;)",
"foo(java.lang.String)",
"foo([LMyTest$Foo;)",
"foo(MyTest$Foo)"},
methodPresentations,
Arrays.toString(methodPresentations));
}
}
@@ -0,0 +1,67 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.java.execution.AbstractTestFrameworkCompilingIntegrationTest;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.PlatformTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
import org.junit.jupiter.api.DisplayName;
import java.util.List;
import java.util.Map;
import static com.intellij.junit6.ServiceMessageUtil.replaceAttributes;
@DisplayName("junit 6 navigation features: location strings, etc")
public class JUnit6NavigationTest extends AbstractTestFrameworkCompilingIntegrationTest {
@Override
protected String getTestContentRoot() {
return VfsUtilCore.pathToUrl(PlatformTestUtil.getCommunityPath() + "/plugins/junit6_rt_tests/testData/integration/navigation");
}
@Override
protected void setupModule() throws Exception {
super.setupModule();
ModuleRootModificationUtil.updateModel(myModule, model -> model.addContentEntry(getTestContentRoot())
.addSourceFolder(getTestContentRoot() + "/test", true));
final ArtifactRepositoryManager repoManager = getRepoManager();
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION), repoManager);
}
public void testNavigation() throws Exception {
ProcessOutput output = doStartTestsProcess(createRunClassConfiguration("org.example.impl.NavTest"));
assertEmpty(output.err);
List<String> messages = output.messages.stream().filter(m -> m.getAttributes().containsKey("locationHint"))
.map(m -> m.getAttributes().get("locationHint").startsWith("file://")
? replaceAttributes(m, Map.of("locationHint", "file://##path##"))
: m.asString())
.toList();
assertEquals(List.of(
"##teamcity[suiteTreeStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' name='fileSourceDynamicTests()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/fileSourceDynamicTests' metainfo='']",
"##teamcity[suiteTreeNode id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' name='methodNavigation()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/methodNavigation' metainfo='']",
"##teamcity[testSuiteStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' name='fileSourceDynamicTests()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/fileSourceDynamicTests' metainfo='']",
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]/|[dynamic-test:#1|]' name='fileSource' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]/|[dynamic-test:#1|]' parentNodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' locationHint='file://##path##']",
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' name='methodNavigation()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/methodNavigation' metainfo='']"
), messages);
}
@NotNull
private RunConfiguration createRunClassConfiguration(final String className) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(className, GlobalSearchScope.projectScope(myProject));
assertNotNull(aClass);
RunConfiguration configuration = createConfiguration(aClass);
assertInstanceOf(configuration, JUnitConfiguration.class);
return configuration;
}
}
@@ -0,0 +1,74 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.java.execution.AbstractTestFrameworkCompilingIntegrationTest;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.PlatformTestUtil;
import jetbrains.buildServer.messages.serviceMessages.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class JUnit6OldSuiteApiIntegrationTest extends AbstractTestFrameworkCompilingIntegrationTest {
@Override
protected String getTestContentRoot() {
return VfsUtilCore.pathToUrl(PlatformTestUtil.getCommunityPath() + "/plugins/junit6_rt_tests/testData/integration/oldSuiteApi");
}
@Override
protected void setupModule() throws Exception {
super.setupModule();
ModuleRootModificationUtil.updateModel(myModule, model -> model.addContentEntry(getTestContentRoot())
.addSourceFolder(getTestContentRoot() + "/test", true));
final ArtifactRepositoryManager repoManager = getRepoManager();
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION), repoManager);
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.platform", "junit-platform-suite-api", JUnit6Constants.VERSION),
repoManager);
}
public void testRunClass() throws Exception {
ProcessOutput processOutput = doStartTestsProcess(createRunClassConfiguration("org.example.impl.MyTest"));
assertEmpty(processOutput.out);
assertEmpty(processOutput.err);
List<ServiceMessage> messages = processOutput.messages;
Map<String, TestStarted> tests = getStartedTests(messages);
assertEquals(Set.of("java:test://org.example.impl.MyTest/test"),
tests.values().stream().map(t -> t.getAttributes().get("locationHint")).collect(Collectors.toSet()));
assertEquals(Set.of(), getTestIds(messages, TestFailed.class));
assertEquals(tests.keySet(), getTestIds(messages, TestFinished.class));
}
@NotNull
private RunConfiguration createRunClassConfiguration(final String className) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(className, GlobalSearchScope.projectScope(myProject));
assertNotNull(aClass);
RunConfiguration configuration = createConfiguration(aClass);
assertInstanceOf(configuration, JUnitConfiguration.class);
return configuration;
}
private static Map<String, TestStarted> getStartedTests(List<ServiceMessage> messages) {
return messages.stream().filter(TestStarted.class::isInstance).map(TestStarted.class::cast)
.collect(Collectors.toMap(t -> t.getAttributes().get("id"), t -> t));
}
private static <T extends BaseTestMessage> Set<String> getTestIds(List<ServiceMessage> messages, Class<T> clazz) {
return messages.stream().filter(clazz::isInstance).map(clazz::cast).map(o -> o.getAttributes().get("id")).collect(Collectors.toSet());
}
}
@@ -0,0 +1,139 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.java.execution.AbstractTestFrameworkCompilingIntegrationTest;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.PlatformTestUtil;
import jetbrains.buildServer.messages.serviceMessages.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class JUnit6SuiteApiIntegrationTest extends AbstractTestFrameworkCompilingIntegrationTest {
@Override
protected String getTestContentRoot() {
return VfsUtilCore.pathToUrl(PlatformTestUtil.getCommunityPath() + "/plugins/junit6_rt_tests/testData/integration/suiteApi");
}
@Override
protected void setupModule() throws Exception {
super.setupModule();
ModuleRootModificationUtil.updateModel(myModule, model -> model.addContentEntry(getTestContentRoot())
.addSourceFolder(getTestContentRoot() + "/test", true));
final ArtifactRepositoryManager repoManager = getRepoManager();
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION), repoManager);
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.platform", "junit-platform-suite-api", JUnit6Constants.VERSION), repoManager);
}
public void testRunClass() throws Exception {
ProcessOutput processOutput = doStartTestsProcess(createRunClassConfiguration("org.example.api.SmokeSuite"));
assertEmpty(processOutput.out);
assertEmpty(processOutput.err);
List<ServiceMessage> messages = processOutput.messages;
Map<String, TestStarted> tests = getStartedTests(messages);
assertEquals(Set.of("java:test://org.example.impl.MyTest/test"),
tests.values().stream().map(t -> t.getAttributes().get("locationHint")).collect(Collectors.toSet()));
assertEquals(Set.of(), getTestIds(messages, TestFailed.class));
assertEquals(tests.keySet(), getTestIds(messages, TestFinished.class));
}
public void testRunPackage() throws Exception {
ProcessOutput processOutput = doStartTestsProcess(createRunPackageConfiguration("org.example.api"));
assertEmpty(processOutput.out);
assertEmpty(processOutput.err);
List<ServiceMessage> messages = processOutput.messages;
Map<String, TestStarted> tests = getStartedTests(messages);
assertEquals(Set.of("java:test://org.example.impl.MyTest/test",
"java:test://org.example.impl.FirstTest/test1",
"java:test://org.example.impl.FirstTest/test2",
"java:test://org.example.impl.SecondTest/test1",
"java:test://org.example.impl.SecondTest/test2"),
tests.values().stream().map(t -> t.getAttributes().get("locationHint")).collect(Collectors.toSet()));
assertEquals(getTestIds(tests, Set.of("java:test://org.example.impl.FirstTest/test1",
"java:test://org.example.impl.SecondTest/test2")),
getTestIds(messages, TestFailed.class));
assertEquals(tests.keySet(), getTestIds(messages, TestFinished.class));
}
@NotNull
private RunConfiguration createRunClassConfiguration(final String className) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(className, GlobalSearchScope.projectScope(myProject));
assertNotNull(aClass);
RunConfiguration configuration = createConfiguration(aClass);
assertInstanceOf(configuration, JUnitConfiguration.class);
return configuration;
}
@NotNull
private RunConfiguration createRunPackageConfiguration(final String packageName) {
PsiPackage aPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName);
assertNotNull(aPackage);
RunConfiguration configuration = createConfiguration(aPackage);
assertInstanceOf(configuration, JUnitConfiguration.class);
return configuration;
}
private static Map<String, TestStarted> getStartedTests(List<ServiceMessage> messages) {
return messages.stream().filter(TestStarted.class::isInstance).map(TestStarted.class::cast)
.collect(Collectors.toMap(t -> t.getAttributes().get("id"), t -> t));
}
private static <T extends BaseTestMessage> Set<String> getTestIds(Map<String, TestStarted> tests, Set<String> locationHints) {
return tests.entrySet().stream()
.filter(e -> locationHints.contains(e.getValue().getAttributes().get("locationHint")))
.map(t -> t.getKey())
.collect(Collectors.toSet());
}
private static <T extends BaseTestMessage> Set<String> getTestIds(List<ServiceMessage> messages, Class<T> clazz) {
return messages.stream().filter(clazz::isInstance).map(clazz::cast).map(o -> o.getAttributes().get("id")).collect(Collectors.toSet());
}
public void testRerunFailedFromSuite() throws Exception {
ProcessOutput initialOutput = doStartTestsProcess(createRunPackageConfiguration("org.example.api"));
assertEmpty(initialOutput.out);
assertEmpty(initialOutput.err);
List<ServiceMessage> initialMessages = initialOutput.messages;
Map<String, TestStarted> started = getStartedTests(initialMessages);
Set<String> failedIds = getTestIds(initialMessages, TestFailed.class);
Set<String> failedHints = failedIds.stream()
.map(id -> started.get(id))
.map(t -> t.getAttributes().get("locationHint"))
.collect(Collectors.toSet());
assertEquals(Set.of(
"java:test://org.example.impl.FirstTest/test1",
"java:test://org.example.impl.SecondTest/test2"
), failedHints);
ProcessOutput rerunOutput = doStartTestsProcess(createRunPackageConfiguration("org.example.api"), failedHints);
assertEmpty(rerunOutput.out);
assertEmpty(rerunOutput.err);
Map<String, TestStarted> rerunStarted = getStartedTests(rerunOutput.messages);
Set<String> rerunHints = rerunStarted.values().stream().map(t -> t.getAttributes().get("locationHint")).collect(Collectors.toSet());
assertEquals(failedHints, rerunHints);
assertEquals(rerunStarted.keySet(), getTestIds(rerunOutput.messages, TestFinished.class));
}
}
@@ -0,0 +1,20 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6;
import jetbrains.buildServer.messages.serviceMessages.ServiceMessage;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedHashMap;
import java.util.Map;
public final class ServiceMessageUtil {
public static @NotNull String replaceAttributes(@NotNull ServiceMessage message, @NotNull Map<String, String> attributes) {
Map<String, String> attrs = new LinkedHashMap<>(message.getAttributes());
for (Map.Entry<String, String> entry : attributes.entrySet()) {
if (attrs.containsKey(entry.getKey())) {
attrs.put(entry.getKey(), entry.getValue());
}
}
return ServiceMessage.asString(message.getMessageName(), attrs);
}
}
@@ -0,0 +1,11 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6.testData;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class AnnotationsTestClass {
@Test
@DisplayName("test's method")
void test1() {}
}
@@ -0,0 +1,45 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit6.testData;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.api.DynamicTest;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
public class MyTestClass {
@Test
void test1(TestReporter reporter) {
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("key1", "value1");
map.put("stdout", "out1");
reporter.publishEntry(map);
Assertions.assertAll("2 errors",
() -> Assertions.assertEquals("expected1", "actual1", "message1"),
() -> Assertions.assertEquals("expected2", "actual2", "message2")
);
}
@TestFactory
Stream<DynamicTest> brokenStream() {
throw new IllegalStateException("broken");
}
@org.junit.jupiter.api.Disabled("container disabled")
@TestFactory
Stream<DynamicTest> brokenStreamDisabled() {
return Stream.of();
}
@org.junit.jupiter.api.Disabled("disabled")
@Test
void disabledTest() {}
}
@@ -0,0 +1,13 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
import org.junit.jupiter.api.Test;
class MyTest {
@Test void foo(int[] i) {}
@Test void foo(int i) {}
@Test void foo(String[] i) {}
@Test void foo(String i) {}
@Test void foo(Foo[] i) {}
@Test void foo(Foo i) {}
static class Foo {}
}
@@ -0,0 +1,32 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.impl;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
public class NavTest {
@Test
void methodNavigation() {}
@TestFactory
Collection<DynamicTest> fileSourceDynamicTests() {
List<DynamicTest> list = new ArrayList<>();
try {
// Create a temporary file to force FileSource in JUnit Platform
URI uri = Files.createTempFile("navtest", ".txt").toUri();
list.add(dynamicTest("fileSource", uri, () -> {}));
} catch (Exception ignored) {
}
return list;
}
}
@@ -0,0 +1,10 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.impl;
import org.junit.jupiter.api.Test;
public class MyTest {
@Test
public void test() {
}
}
@@ -0,0 +1,12 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.api;
import org.example.impl.FirstTest;
import org.example.impl.SecondTest;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectClasses({FirstTest.class, SecondTest.class})
public class AllTests {
}
@@ -0,0 +1,11 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.api;
import org.example.impl.MyTest;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectClasses({MyTest.class})
public class SmokeSuite {
}
@@ -0,0 +1,15 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.impl;
import org.junit.jupiter.api.Test;
public class FirstTest {
@Test
public void test1() {
throw new RuntimeException();
}
@Test
public void test2() {
}
}
@@ -0,0 +1,10 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.impl;
import org.junit.jupiter.api.Test;
public class MyTest {
@Test
public void test() {
}
}
@@ -0,0 +1,15 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.impl;
import org.junit.jupiter.api.Test;
public class SecondTest {
@Test
public void test1() {
}
@Test
public void test2() {
throw new RuntimeException();
}
}
@@ -7,6 +7,7 @@ import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -14,7 +15,6 @@ import java.util.List;
/**
* Before rename or move
*
* @noinspection HardCodedStringLiteral
* @see com.intellij.execution.junit.JUnitConfiguration#JUNIT_START_CLASS
*/
public final class JUnitStarter {
@@ -24,12 +24,14 @@ public final class JUnitStarter {
public static final String JUNIT3_PARAMETER = "-junit3";
public static final String JUNIT4_PARAMETER = "-junit4";
public static final String JUNIT5_PARAMETER = "-junit5";
public static final String JUNIT6_PARAMETER = "-junit6";
private static final String JUNIT5_KEY = "idea.is.junit5";
private static final String SOCKET = "-socket";
private static final String JUNIT3_RUNNER_NAME = "com.intellij.junit3.JUnit3IdeaTestRunner";
private static final String JUNIT4_RUNNER_NAME = "com.intellij.junit4.JUnit4IdeaTestRunner";
private static final String JUNIT5_RUNNER_NAME = "com.intellij.junit5.JUnit5IdeaTestRunner";
private static final String JUNIT6_RUNNER_NAME = "com.intellij.junit6.JUnit6IdeaTestRunner";
private static String ourForkMode;
private static String ourCommandFileName;
private static String ourWorkingDirs;
@@ -44,13 +46,14 @@ public final class JUnitStarter {
String agentName = processParameters(argList, listeners, name);
if (!JUNIT5_RUNNER_NAME.equals(agentName) && !canWorkWithJUnitVersion(System.err, agentName)) {
if (!Arrays.asList(JUNIT5_RUNNER_NAME, JUNIT6_RUNNER_NAME).contains(agentName) && !canWorkWithJUnitVersion(System.err, agentName)) {
System.exit(-3);
}
if (!checkVersion(args, System.err)) {
System.exit(-3);
}
@SuppressWarnings("SSBasedInspection")
String[] array = argList.toArray(new String[0]);
int exitCode = prepareStreamsAndStart(array, agentName, listeners, name[0]);
System.exit(exitCode);
@@ -72,6 +75,9 @@ public final class JUnitStarter {
else if (arg.equals(JUNIT5_PARAMETER)) {
agentName = JUNIT5_RUNNER_NAME;
}
else if (arg.equals(JUNIT6_PARAMETER)) {
agentName = JUNIT6_RUNNER_NAME;
}
else {
if (arg.startsWith("@name")) {
params[0] = arg.substring("@name".length());
@@ -243,7 +249,7 @@ public final class JUnitStarter {
public static void printClassesList(List<String> classNames, String packageName, String category, String filters, File tempFile)
throws IOException {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.UTF_8))) {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(Files.newOutputStream(tempFile.toPath()), StandardCharsets.UTF_8))) {
writer.println(packageName); //package name
writer.println(category); //category
writer.println(filters); //patterns
@@ -20,12 +20,12 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
abstract class AbstractKotlinPsiBasedTestFramework : KotlinPsiBasedTestFramework {
protected abstract val markerClassFqn: String
protected abstract val markerClassFqns: Collection<String>
protected abstract val disabledTestAnnotation: String
protected abstract val allowTestMethodsInObject: Boolean
protected open fun isFrameworkAvailable(element: KtElement): Boolean =
isFrameworkAvailable(element, markerClassFqn, true)
markerClassFqns.any { isFrameworkAvailable(element, it, true) }
protected fun isFrameworkAvailable(element: KtElement, markerClassFqn: String, javaOnly: Boolean): Boolean {
val module = element.module ?: return false
@@ -39,10 +39,10 @@ interface KotlinPsiBasedTestFramework {
fun findTearDown(classOrObject: KtClassOrObject): KtNamedFunction? = null
companion object {
const val KOTLIN_TEST_TEST = "kotlin.test.Test"
const val KOTLIN_TEST_IGNORE = "kotlin.test.Ignore"
const val KOTLIN_TEST_BEFORE_TEST = "kotlin.test.BeforeTest"
const val KOTLIN_TEST_AFTER_TEST = "kotlin.test.AfterTest"
const val KOTLIN_TEST_TEST: String = "kotlin.test.Test"
const val KOTLIN_TEST_IGNORE: String = "kotlin.test.Ignore"
const val KOTLIN_TEST_BEFORE_TEST: String = "kotlin.test.BeforeTest"
const val KOTLIN_TEST_AFTER_TEST: String = "kotlin.test.AfterTest"
fun PsiElement?.asKtClassOrObject(): KtClassOrObject? =
when (this) {
@@ -13,6 +13,8 @@
order="before junit4"/>
<testFramework implementation="org.jetbrains.kotlin.idea.junit.KotlinJUnit5Framework"
order="before junit5"/>
<testFramework implementation="org.jetbrains.kotlin.idea.junit.KotlinJUnit6Framework"
order="before junit6"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin.idea">
@@ -33,8 +33,8 @@ import org.jetbrains.kotlin.psi.psiUtil.isPrivate
class KotlinJUnit3Framework: JUnit3Framework(), KotlinPsiBasedTestFramework {
private val psiBasedDelegate = object : AbstractKotlinPsiBasedTestFramework() {
override val markerClassFqn: String
get() = JUnitUtil.TEST_CASE_CLASS
override val markerClassFqns: Collection<String>
get() = listOf(JUnitUtil.TEST_CASE_CLASS)
override val disabledTestAnnotation: String
get() = throw UnsupportedOperationException("JUnit3 does not support Ignore methods")
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.psiUtil.isPrivate
class KotlinJUnit4Framework: JUnit4Framework(), KotlinPsiBasedTestFramework {
private val psiBasedDelegate = object : AbstractKotlinPsiBasedTestFramework() {
override val markerClassFqn: String = JUnitUtil.TEST_ANNOTATION
override val markerClassFqns: Collection<String> = listOf(JUnitUtil.TEST_ANNOTATION)
override val disabledTestAnnotation: String = "org.junit.Ignore"
override val allowTestMethodsInObject: Boolean = false
@@ -28,8 +28,8 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
class KotlinJUnit5Framework: JUnit5Framework(), KotlinPsiBasedTestFramework {
private val psiBasedDelegate = object : AbstractKotlinPsiBasedTestFramework() {
override val markerClassFqn: String = JUnitUtil.TEST5_ANNOTATION
override val disabledTestAnnotation: String = "org.junit.jupiter.api.Disabled"
override val markerClassFqns: Collection<String> = markerClassFQNames
override val disabledTestAnnotation: String = JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_DISABLED
override val allowTestMethodsInObject: Boolean = true
override fun checkTestClass(declaration: KtClassOrObject): ThreeState {
@@ -83,7 +83,7 @@ class KotlinJUnit5Framework: JUnit5Framework(), KotlinPsiBasedTestFramework {
NO
} else if (declaration is KtClass && declaration.isInner()) {
if (isAnnotated(declaration, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_NESTED)) YES else NO
} else if (declaration.isTopLevel() && isAnnotated(declaration, "org.junit.jupiter.api.extension.ExtendWith")) {
} else if (declaration.isTopLevel() && isAnnotated(declaration, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_EXTENSION_EXTEND_WITH)) {
YES
} else if (findAnnotatedFunction(declaration, testableAnnotations) != null) {
YES
@@ -188,9 +188,9 @@ class KotlinJUnit5Framework: JUnit5Framework(), KotlinPsiBasedTestFramework {
private val METHOD_ANNOTATION_FQN = setOf(
JUnitUtil.TEST5_ANNOTATION,
KotlinPsiBasedTestFramework.KOTLIN_TEST_TEST,
"org.junit.jupiter.params.ParameterizedTest",
"org.junit.jupiter.api.RepeatedTest",
"org.junit.jupiter.api.TestFactory",
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST,
JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_REPEATED_TEST,
JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_FACTORY,
"org.junit.jupiter.api.TestTemplate",
"org.junitpioneer.jupiter.RetryingTest"
)
@@ -0,0 +1,201 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.junit
import com.intellij.execution.junit.JUnit6Framework
import com.intellij.execution.junit.JUnitUtil
import com.intellij.ide.fileTemplates.FileTemplateDescriptor
import com.intellij.java.analysis.OuterModelsModificationTrackerManager
import com.intellij.lang.Language
import com.intellij.psi.PsiElement
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.parentOfType
import com.intellij.util.ThreeState
import com.intellij.util.ThreeState.*
import com.siyeh.ig.junit.JUnitCommonClassNames
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider
import org.jetbrains.kotlin.idea.testIntegration.framework.AbstractKotlinPsiBasedTestFramework
import org.jetbrains.kotlin.idea.testIntegration.framework.KotlinPsiBasedTestFramework
import org.jetbrains.kotlin.idea.testIntegration.framework.KotlinPsiBasedTestFramework.Companion.asKtClassOrObject
import org.jetbrains.kotlin.idea.testIntegration.framework.KotlinPsiBasedTestFramework.Companion.asKtNamedFunction
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
class KotlinJUnit6Framework: JUnit6Framework(), KotlinPsiBasedTestFramework {
private val psiBasedDelegate = object : AbstractKotlinPsiBasedTestFramework() {
override val markerClassFqns: Collection<String> = markerClassFQNames
override val disabledTestAnnotation: String = JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_DISABLED
override val allowTestMethodsInObject: Boolean = true
override fun checkTestClass(declaration: KtClassOrObject): ThreeState {
val checkState = super.checkTestClass(declaration)
if (checkState != UNSURE) return checkState
return CachedValuesManager.getCachedValue(declaration) {
CachedValueProvider.Result.create(
checkJUnit6TestClass(declaration),
OuterModelsModificationTrackerManager.getTracker(declaration.project)
)
}
}
fun isPotentialTestClass(element: PsiElement): Boolean {
if (element.language != KotlinLanguage.INSTANCE) return false
val psiElement = (element as? KtLightElement<*, *>)?.kotlinOrigin ?: element
val ktClassOrObject = psiElement.parentOfType<KtClassOrObject>(true) ?: return false
return CachedValuesManager.getCachedValue(ktClassOrObject) {
CachedValueProvider.Result.create(
checkJUnit6PotentialTestClass(ktClassOrObject) != NO,
OuterModelsModificationTrackerManager.getTracker(ktClassOrObject.project)
)
}
}
override fun isTestMethod(declaration: KtNamedFunction): Boolean {
if (!super.isTestMethod(declaration)) return false
if (declaration.annotationEntries.isEmpty()) return false
return isJUnit6TestMethod(declaration)
}
private fun checkJUnit6TestClass(declaration: KtClassOrObject): ThreeState =
if (!isFrameworkAvailable(declaration)) {
NO
} else {
checkIsJUnit6LikeTestClass(declaration, false)
}
private fun checkJUnit6PotentialTestClass(declaration: KtClassOrObject): ThreeState =
if (!isFrameworkAvailable(declaration) && !isFrameworkAvailable(declaration, KotlinPsiBasedTestFramework.KOTLIN_TEST_TEST, false)) {
NO
} else {
checkIsJUnit6LikeTestClass(declaration, true)
}
private fun checkIsJUnit6LikeTestClass(declaration: KtClassOrObject, isPotential: Boolean): ThreeState =
if (isPotential) {
if (isUnderTestSources(declaration)) UNSURE else NO
} else if (!isFrameworkAvailable(declaration)) {
NO
} else if (declaration is KtClass && declaration.isInner()) {
if (isAnnotated(declaration, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_NESTED)) YES else NO
} else if (declaration.isTopLevel() && isAnnotated(declaration, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_EXTENSION_EXTEND_WITH)) {
YES
} else if (findAnnotatedFunction(declaration, testableAnnotations) != null) {
YES
} else {
UNSURE
}
private fun isJUnit6TestMethod(method: KtNamedFunction): Boolean {
return isAnnotated(method, METHOD_ANNOTATION_FQN)
}
override fun findSetUp(classOrObject: KtClassOrObject): KtNamedFunction? =
findAnnotatedFunction(classOrObject.takeIf { isTestClass(it) }, setUpAnnotations)
override fun findTearDown(classOrObject: KtClassOrObject): KtNamedFunction? =
findAnnotatedFunction(classOrObject.takeIf { isTestClass(it) }, tearDownAnnotations)
}
override fun responsibleFor(declaration: KtNamedDeclaration): Boolean =
psiBasedDelegate.responsibleFor(declaration)
override fun checkTestClass(declaration: KtClassOrObject): ThreeState = psiBasedDelegate.checkTestClass(declaration)
override fun isTestClass(clazz: PsiElement): Boolean =
when (val checkTestClass = checkTestClass(clazz)) {
UNSURE -> super.isTestClass(clazz)
else -> checkTestClass == YES
}
override fun isPotentialTestClass(clazz: PsiElement): Boolean =
isTestClass(clazz) || psiBasedDelegate.isPotentialTestClass(clazz)
override fun findSetUpMethod(clazz: PsiElement): PsiElement? =
when (checkTestClass(clazz)) {
UNSURE -> super.findSetUpMethod(clazz)
NO -> null
else -> clazz.asKtClassOrObject()?.let(psiBasedDelegate::findSetUp)
}
override fun findTearDownMethod(clazz: PsiElement): PsiElement? =
when (checkTestClass(clazz)) {
UNSURE -> super.findTearDownMethod(clazz)
NO -> null
else -> clazz.asKtClassOrObject()?.let(psiBasedDelegate::findTearDown)
}
override fun isIgnoredMethod(element: PsiElement?): Boolean =
when (checkTestClass(element)) {
UNSURE -> super.isIgnoredMethod(element)
NO -> false
else -> element.asKtNamedFunction()?.let(psiBasedDelegate::isIgnoredMethod) ?: false
}
override fun isTestMethod(element: PsiElement?): Boolean =
when (checkTestClass(element)) {
UNSURE -> super.isTestMethod(element)
NO -> false
else -> element.asKtNamedFunction()?.let(psiBasedDelegate::isTestMethod) ?: false
}
override fun getLanguage(): Language = KotlinLanguage.INSTANCE
override fun isTestMethod(declaration: KtNamedFunction): Boolean =
psiBasedDelegate.isTestMethod(declaration)
override fun isIgnoredMethod(declaration: KtNamedFunction): Boolean =
psiBasedDelegate.isIgnoredMethod(declaration)
override fun getSetUpMethodFileTemplateDescriptor(): FileTemplateDescriptor? {
return if (KotlinPluginModeProvider.isK1Mode()) {
super.getSetUpMethodFileTemplateDescriptor()
} else {
FileTemplateDescriptor("Kotlin JUnit5 SetUp Function.kt")
}
}
override fun getTearDownMethodFileTemplateDescriptor(): FileTemplateDescriptor? {
return if (KotlinPluginModeProvider.isK1Mode()) {
super.getTearDownMethodFileTemplateDescriptor()
} else {
FileTemplateDescriptor("Kotlin JUnit5 TearDown Function.kt")
}
}
override fun getTestMethodFileTemplateDescriptor(): FileTemplateDescriptor {
return if (KotlinPluginModeProvider.isK1Mode()) {
super.getTestMethodFileTemplateDescriptor()
} else {
FileTemplateDescriptor("Kotlin JUnit5 Test Function.kt")
}
}
override fun getTestClassFileTemplateDescriptor(): FileTemplateDescriptor? =
if (KotlinPluginModeProvider.isK1Mode()) {
super.getTestClassFileTemplateDescriptor()
} else {
FileTemplateDescriptor("Kotlin JUnit5 Test Class.kt")
}
}
private val METHOD_ANNOTATION_FQN = setOf(
JUnitUtil.TEST5_ANNOTATION,
KotlinPsiBasedTestFramework.KOTLIN_TEST_TEST,
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST,
JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_REPEATED_TEST,
JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_FACTORY,
"org.junit.jupiter.api.TestTemplate",
"org.junitpioneer.jupiter.RetryingTest"
)
private val setUpAnnotations = setOf(JUnitUtil.BEFORE_EACH_ANNOTATION_NAME, KotlinPsiBasedTestFramework.KOTLIN_TEST_BEFORE_TEST)
private val tearDownAnnotations = setOf(JUnitUtil.AFTER_EACH_ANNOTATION_NAME, KotlinPsiBasedTestFramework.KOTLIN_TEST_AFTER_TEST)
private val testableAnnotations = METHOD_ANNOTATION_FQN + setUpAnnotations + tearDownAnnotations
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class KotlinTestNGFramework: TestNGFramework(), KotlinPsiBasedTestFramework {
private val psiBasedDelegate = object : AbstractKotlinPsiBasedTestFramework() {
override val markerClassFqn: String = TestNGUtil.TEST_ANNOTATION_FQN
override val markerClassFqns: Collection<String> = listOf(TestNGUtil.TEST_ANNOTATION_FQN)
override val disabledTestAnnotation: String
get() = throw UnsupportedOperationException("TestNG does not support Ignore methods")
override val allowTestMethodsInObject: Boolean = false