diff --git a/platforms/ide/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/TestOperationMapper.java b/platforms/ide/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/TestOperationMapper.java index e0b4da59c0da..866c8168e6f6 100644 --- a/platforms/ide/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/TestOperationMapper.java +++ b/platforms/ide/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/TestOperationMapper.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList; import org.gradle.api.internal.tasks.testing.AbstractTestDescriptor; import org.gradle.api.internal.tasks.testing.DecoratingTestDescriptor; +import org.gradle.api.internal.tasks.testing.DefaultParameterizedTestDescriptor; import org.gradle.api.internal.tasks.testing.operations.ExecuteTestBuildOperationType; import org.gradle.api.tasks.testing.TestDescriptor; import org.gradle.api.tasks.testing.TestFailure; @@ -37,6 +38,7 @@ import org.gradle.internal.operations.OperationFinishEvent; import org.gradle.internal.operations.OperationIdentifier; import org.gradle.internal.operations.OperationStartEvent; +import org.gradle.tooling.events.OperationDescriptor; import org.gradle.tooling.events.OperationType; import org.gradle.tooling.internal.protocol.InternalFailure; import org.gradle.tooling.internal.protocol.events.InternalJvmTestDescriptor; @@ -93,6 +95,7 @@ private DefaultTestDescriptor toTestDescriptorForSuite(OperationIdentifier build TestDescriptor originalDescriptor = getOriginalDescriptor(suite); if (originalDescriptor instanceof AbstractTestDescriptor) { methodName = ((AbstractTestDescriptor) originalDescriptor).getMethodName(); + operationDisplayName = adjustOperationDisplayNameForIntelliJ(operationDisplayName, (AbstractTestDescriptor) originalDescriptor); } else { operationDisplayName = getLegacyOperationDisplayName(operationDisplayName, originalDescriptor); } @@ -103,12 +106,31 @@ private DefaultTestDescriptor toTestDescriptorForTest(OperationIdentifier buildO String operationDisplayName = test.toString(); TestDescriptor originalDescriptor = getOriginalDescriptor(test); - if (!(originalDescriptor instanceof AbstractTestDescriptor)) { + if (originalDescriptor instanceof AbstractTestDescriptor) { + operationDisplayName = adjustOperationDisplayNameForIntelliJ(operationDisplayName, (AbstractTestDescriptor) originalDescriptor); + } else { operationDisplayName = getLegacyOperationDisplayName(operationDisplayName, originalDescriptor); } return new DefaultTestDescriptor(buildOperationId, test.getName(), operationDisplayName, test.getDisplayName(), InternalJvmTestDescriptor.KIND_ATOMIC, null, test.getClassName(), test.getName(), parentId, taskTracker.getTaskPath(buildOperationId)); } + /** + * This is a workaround to preserve backward compatibility with IntelliJ IDEA. + * The problem only occurs in IntelliJ IDEA because it parses {@link OperationDescriptor#getDisplayName()} to get the test display name. + * Once its code is updated to use {@link org.gradle.tooling.events.test.TestOperationDescriptor#getTestDisplayName()}, the workaround can be removed as well. + * Alternatively, it can be removed in Gradle 9.0. + * See this issue for more details. + */ + private String adjustOperationDisplayNameForIntelliJ(String operationDisplayName, AbstractTestDescriptor descriptor) { + String displayName = descriptor.getDisplayName(); + if (!descriptor.getName().equals(displayName) && !(descriptor.getClassDisplayName() != null && descriptor.getName().endsWith(descriptor.getClassDisplayName()))) { + return descriptor.getDisplayName(); + } else if (descriptor instanceof DefaultParameterizedTestDescriptor) { // for spock parameterized tests + return descriptor.getDisplayName(); + } + return operationDisplayName; + } + /** * This is a workaround for Kotlin Gradle Plugin overriding TestDescriptor. * The problem only occurs in IntelliJ IDEA with multiplatform projects. @@ -125,7 +147,8 @@ private static String getLegacyOperationDisplayName(String operationDisplayName, } /** - * can be removed once {@link #getLegacyOperationDisplayName(String, TestDescriptor) the workaround above} is removed + * can be removed once the workaround above ({@link #getLegacyOperationDisplayName(String, TestDescriptor) 1} and + * {@link #adjustOperationDisplayNameForIntelliJ(String, AbstractTestDescriptor) 2}) are removed */ private static TestDescriptor getOriginalDescriptor(TestDescriptor testDescriptor) { if (testDescriptor instanceof DecoratingTestDescriptor) { diff --git a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/TestLauncherSpec.groovy b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/TestLauncherSpec.groovy index 7d2854566315..39cccbd91606 100644 --- a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/TestLauncherSpec.groovy +++ b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/TestLauncherSpec.groovy @@ -318,6 +318,8 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO } interface TestEventSpec { + void operationDisplayName(String displayName) + void testDisplayName(String displayName) void suite(String name, @DelegatesTo(value = TestEventSpec, strategy = Closure.DELEGATE_FIRST) Closure spec) @@ -343,7 +345,7 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO if (task == null) { throw new AssertionError("Expected to find a test task $path but none was found") } - DefaultTestEventSpec.assertSpec(task.parent, testEvents, verifiedEvents, rootSpec) + DefaultTestEventSpec.assertSpec(task.parent, testEvents, verifiedEvents, "Task $path", rootSpec) } } @@ -353,14 +355,15 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO private final Set verifiedEvents private final OperationDescriptor parent private String testDisplayName + private String operationDisplayName - static void assertSpec(OperationDescriptor descriptor, List testEvents, Set verifiedEvents, @DelegatesTo(value = TestEventSpec, strategy = Closure.DELEGATE_FIRST) Closure spec) { + static void assertSpec(OperationDescriptor descriptor, List testEvents, Set verifiedEvents, String expectedOperationDisplayName, @DelegatesTo(value = TestEventSpec, strategy = Closure.DELEGATE_FIRST) Closure spec) { verifiedEvents.add(descriptor) DefaultTestEventSpec childSpec = new DefaultTestEventSpec(descriptor, testEvents, verifiedEvents) spec.delegate = childSpec spec.resolveStrategy = Closure.DELEGATE_FIRST spec() - childSpec.validate() + childSpec.validate(expectedOperationDisplayName) } DefaultTestEventSpec(OperationDescriptor parent, List testEvents, Set verifiedEvents) { @@ -369,6 +372,11 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO this.verifiedEvents = verifiedEvents } + @Override + void operationDisplayName(String displayName) { + this.operationDisplayName = displayName + } + @Override void testDisplayName(String displayName) { this.testDisplayName = displayName @@ -394,12 +402,8 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO if (child == null) { failWith("test suite", name) } - if (name.startsWith("Gradle Test")) { - assert normalizeExecutor(child.displayName) == name - } else { - assert child.displayName == "Test suite '$name'" - } - assertSpec(child, testEvents, verifiedEvents, spec) + String expectedOperationDisplayName = name.startsWith("Gradle Test") ? normalizeExecutor(child.displayName) : "Test suite '$name'" + assertSpec(child, testEvents, verifiedEvents, expectedOperationDisplayName, spec) } @Override @@ -415,8 +419,7 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO if (child == null) { failWith("test class", name) } - assert child.displayName == "Test class $name" - assertSpec(child, testEvents, verifiedEvents, spec) + assertSpec(child, testEvents, verifiedEvents, "Test class $name", spec) } @Override @@ -434,8 +437,7 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO if (child == null) { failWith("test", name) } - assert child.displayName == "Test $name($expectedClassName)" - assertSpec(child, testEvents, verifiedEvents, spec) + assertSpec(child, testEvents, verifiedEvents, "Test $name($expectedClassName)", spec) } @Override @@ -453,8 +455,7 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO if (child == null) { failWith("test method suite", name) } - assert child.displayName == "Test method $name" - assertSpec(child, testEvents, verifiedEvents, spec) + assertSpec(child, testEvents, verifiedEvents, "Test method $name", spec) } private void failWith(String what, String name) { @@ -471,10 +472,15 @@ abstract class TestLauncherSpec extends ToolingApiSpecification implements WithO throw err.build() } - void validate() { + void validate(String expectedOperationDisplayName) { if (testDisplayName != null && parent.respondsTo("getTestDisplayName")) { assert testDisplayName == ((TestOperationDescriptor) parent).testDisplayName } + if (operationDisplayName != null) { + assert operationDisplayName == normalizeExecutor(parent.displayName) + } else { + assert expectedOperationDisplayName == normalizeExecutor(parent.displayName) + } } } diff --git a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r70/TestDisplayNameJUnit5CrossVersionSpec.groovy b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r70/TestDisplayNameJUnit5CrossVersionSpec.groovy index e6f357293ca1..28e98bda46d5 100644 --- a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r70/TestDisplayNameJUnit5CrossVersionSpec.groovy +++ b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r70/TestDisplayNameJUnit5CrossVersionSpec.groovy @@ -78,8 +78,10 @@ public class SimpleTests { suite("Gradle Test Run :test") { suite("Gradle Test Executor") { testClass("org.example.SimpleTests") { + operationDisplayName "a class display name" testDisplayName "a class display name" test("test()") { + operationDisplayName "and a test display name" testDisplayName "and a test display name" } } @@ -191,29 +193,40 @@ class TestingAStackDemo { suite("Gradle Test Run :test") { suite("Gradle Test Executor") { testClass("org.example.TestingAStackDemo") { + operationDisplayName "A stack" testDisplayName "A stack" test("isInstantiatedWithNew()") { + operationDisplayName "is instantiated with new Stack()" testDisplayName "is instantiated with new Stack()" } testClass("org.example.TestingAStackDemo\$WhenNew") { + operationDisplayName "when new" testDisplayName "when new" test("isEmpty()") { + operationDisplayName "is empty" testDisplayName "is empty" } test("throwsExceptionWhenPeeked()") { + operationDisplayName "throws EmptyStackException when peeked" testDisplayName "throws EmptyStackException when peeked" } test("throwsExceptionWhenPopped()") { + operationDisplayName "throws EmptyStackException when popped" testDisplayName "throws EmptyStackException when popped" } testClass("org.example.TestingAStackDemo\$WhenNew\$AfterPushing") { + operationDisplayName "after pushing an element" + testDisplayName "after pushing an element" test("isNotEmpty()") { + operationDisplayName "it is no longer empty" testDisplayName "it is no longer empty" } test("returnElementWhenPeeked()") { + operationDisplayName "returns the element when peeked but remains not empty" testDisplayName "returns the element when peeked but remains not empty" } test("returnElementWhenPopped()") { + operationDisplayName "returns the element when popped and is empty" testDisplayName "returns the element when popped and is empty" } } @@ -265,22 +278,29 @@ public class ParameterizedTests { suite("Gradle Test Run :test") { suite("Gradle Test Executor") { testClass("org.example.ParameterizedTests") { + operationDisplayName "Parameterized test" testDisplayName "Parameterized test" testMethodSuite("test1(String)") { + operationDisplayName "1st test" testDisplayName "1st test" test("test1(String)[1]") { + operationDisplayName "[1] foo" testDisplayName "[1] foo" } test("test1(String)[2]") { + operationDisplayName "[2] bar" testDisplayName "[2] bar" } } testMethodSuite("test2(String)") { + operationDisplayName "2nd test" testDisplayName "2nd test" test("test2(String)[1]") { + operationDisplayName "1 ==> the test for 'foo'" testDisplayName "1 ==> the test for 'foo'" } test("test2(String)[2]") { + operationDisplayName "2 ==> the test for 'bar'" testDisplayName "2 ==> the test for 'bar'" } } @@ -345,23 +365,30 @@ public class DynamicTests { testClass("org.example.DynamicTests") { testDisplayName "DynamicTests" testMethodSuite("testFactory()") { + operationDisplayName "testFactory()" testDisplayName "testFactory()" testMethodSuite("testFactory()[1]") { + operationDisplayName "some container" testDisplayName "some container" testMethodSuite("testFactory()[1][1]") { + operationDisplayName "some nested container" testDisplayName "some nested container" test("testFactory()[1][1][1]") { + operationDisplayName "foo" testDisplayName "foo" } test("testFactory()[1][1][2]") { + operationDisplayName "bar" testDisplayName "bar" } } } } testMethodSuite("anotherTestFactory()") { + operationDisplayName "another test factory" testDisplayName "another test factory" test("anotherTestFactory()[1]") { + operationDisplayName "foo" testDisplayName "foo" } } @@ -423,32 +450,42 @@ public class ComplexTests { suite("Gradle Test Run :test") { suite("Gradle Test Executor") { testClass("org.example.ComplexTests") { + operationDisplayName "some_name for_tests" testDisplayName "some_name for_tests" test("test()") { + operationDisplayName "test" testDisplayName "test" } test("simple_test()") { + operationDisplayName "simple test" testDisplayName "simple test" } test("ugly_test()") { + operationDisplayName "pretty pretty_test" testDisplayName "pretty pretty_test" } testMethodSuite("parametrized_test(int, String)") { + operationDisplayName "parametrized test (int, String)" testDisplayName "parametrized test (int, String)" test("parametrized_test(int, String)[1]") { + operationDisplayName "[1] 10, first" testDisplayName "[1] 10, first" } test("parametrized_test(int, String)[2]") { + operationDisplayName "[2] 20, second" testDisplayName "[2] 20, second" } } testMethodSuite("ugly_parametrized_test(int, String)") { + operationDisplayName "pretty parametrized test" testDisplayName "pretty parametrized test" test("ugly_parametrized_test(int, String)[1]") { + operationDisplayName "[1] 30, third" testDisplayName "[1] 30, third" } test("ugly_parametrized_test(int, String)[2]") { + operationDisplayName "[2] 40, fourth" testDisplayName "[2] 40, fourth" } } diff --git a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r75/CustomTestTaskProgressEventCrossVersionTest.groovy b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r75/CustomTestTaskProgressEventCrossVersionTest.groovy index fd204cbdd577..0426468d3787 100644 --- a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r75/CustomTestTaskProgressEventCrossVersionTest.groovy +++ b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r75/CustomTestTaskProgressEventCrossVersionTest.groovy @@ -49,9 +49,7 @@ class CustomTestTaskProgressEventCrossVersionTest extends ToolingApiSpecificatio void statusChanged(ProgressEvent event) { if (event.descriptor.displayName == "Test suite 'MyCustomTestRoot'") { suiteEvent = event - } else if (targetDist.hasTestDisplayNames && event.descriptor.displayName == 'Test MyCustomTest(org.my.MyClass)') { - testEvent = event - } else if (!targetDist.hasTestDisplayNames && event.descriptor.displayName == 'org.my.MyClass descriptor') { + } else if (event.descriptor.displayName == 'org.my.MyClass descriptor') { testEvent = event } } diff --git a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r88/TestDisplayNameSpockCrossVersionSpec.groovy b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r88/TestDisplayNameSpockCrossVersionSpec.groovy index fdecc6745aa8..73eb718e6d96 100644 --- a/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r88/TestDisplayNameSpockCrossVersionSpec.groovy +++ b/platforms/ide/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r88/TestDisplayNameSpockCrossVersionSpec.groovy @@ -125,6 +125,7 @@ class ParameterizedTests extends Specification { testClass("org.example.ParameterizedTests") { testDisplayName "ParameterizedTests" testMethodSuite("length of #name is #length") { + operationDisplayName "length of #name is #length" testDisplayName "length of #name is #length" test("length of Spock is 5") { testDisplayName "length of Spock is 5"