Skip to content

Commit 200a91b

Browse files
committed
feat - Support delegate tests to Gradle build Server
1 parent 144c865 commit 200a91b

File tree

12 files changed

+885
-0
lines changed

12 files changed

+885
-0
lines changed

extension/.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ package.json
88
lib/
99
src/proto/
1010
build/
11+
src/java-test-runner.api.ts

extension/jdtls.ext/com.microsoft.gradle.bs.importer/plugin.xml

+5
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,9 @@
4242
<dynamicReference class="org.eclipse.jdt.internal.core.DynamicProjectReferences"/>
4343
</builder>
4444
</extension>
45+
<extension point="org.eclipse.jdt.ls.core.delegateCommandHandler">
46+
<delegateCommandHandler class="com.microsoft.gradle.bs.importer.handler.GradleDelegateCommandHandler">
47+
<command id="java.gradle.delegateTest" />
48+
</delegateCommandHandler>
49+
</extension>
4550
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
package ch.epfl.scala.bsp4j.extended;
5+
6+
import java.util.Objects;
7+
8+
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
9+
10+
import ch.epfl.scala.bsp4j.TestFinish;
11+
import ch.epfl.scala.bsp4j.TestStatus;
12+
13+
/**
14+
* Extended {@link TestFinish}, which contains the Suite, class, method.
15+
* {@link TestFinish} only contains file location which Gradle doesn't have.
16+
*/
17+
public class TestFinishEx extends TestFinish {
18+
19+
private TestName testName;
20+
21+
private String stackTrace;
22+
23+
/**
24+
* Create a new instance of {@link TestFinishEx}.
25+
*/
26+
public TestFinishEx(@NonNull String displayName, @NonNull TestStatus status,
27+
@NonNull TestName testName) {
28+
super(displayName, status);
29+
this.testName = testName;
30+
}
31+
32+
33+
public TestName getTestName() {
34+
return testName;
35+
}
36+
37+
public void setTestName(TestName testName) {
38+
this.testName = testName;
39+
}
40+
41+
public String getStackTrace() {
42+
return stackTrace;
43+
}
44+
45+
public void setStackTrace(String stackTrace) {
46+
this.stackTrace = stackTrace;
47+
}
48+
49+
@Override
50+
public int hashCode() {
51+
final int prime = 31;
52+
int result = super.hashCode();
53+
result = prime * result + Objects.hash(testName, stackTrace);
54+
return result;
55+
}
56+
57+
@Override
58+
public boolean equals(Object obj) {
59+
if (this == obj) {
60+
return true;
61+
}
62+
if (!super.equals(obj)) {
63+
return false;
64+
}
65+
if (getClass() != obj.getClass()) {
66+
return false;
67+
}
68+
TestFinishEx other = (TestFinishEx) obj;
69+
return Objects.equals(testName, other.testName)
70+
&& Objects.equals(stackTrace, other.stackTrace);
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
package ch.epfl.scala.bsp4j.extended;
5+
6+
import java.util.Objects;
7+
8+
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
9+
10+
/**
11+
* BSP TestName, which contains the test name and the test hierarchy
12+
* e.g. method/class/suite
13+
*/
14+
public class TestName {
15+
16+
private String displayName;
17+
18+
private String suiteName;
19+
20+
private String className;
21+
22+
private String methodName;
23+
24+
private TestName parent;
25+
26+
/**
27+
* Create a new instance of {@link TestName}.
28+
*/
29+
public TestName(@NonNull String displayName, String suiteName,
30+
String className, String methodName) {
31+
this.displayName = displayName;
32+
this.suiteName = suiteName;
33+
this.className = className;
34+
this.methodName = methodName;
35+
}
36+
37+
public String getDisplayName() {
38+
return displayName;
39+
}
40+
41+
public void setDisplayName(String displayName) {
42+
this.displayName = displayName;
43+
}
44+
45+
public String getSuiteName() {
46+
return suiteName;
47+
}
48+
49+
public void setSuiteName(String suiteName) {
50+
this.suiteName = suiteName;
51+
}
52+
53+
public String getClassName() {
54+
return className;
55+
}
56+
57+
public void setClassName(String className) {
58+
this.className = className;
59+
}
60+
61+
public String getMethodName() {
62+
return methodName;
63+
}
64+
65+
public void setMethodName(String methodName) {
66+
this.methodName = methodName;
67+
}
68+
69+
public TestName getParent() {
70+
return parent;
71+
}
72+
73+
public void setParent(TestName parent) {
74+
this.parent = parent;
75+
}
76+
77+
@Override
78+
public int hashCode() {
79+
final int prime = 31;
80+
int result = super.hashCode();
81+
result = prime * result + Objects.hash(displayName, suiteName, className, methodName, parent);
82+
return result;
83+
}
84+
85+
@Override
86+
public boolean equals(Object obj) {
87+
if (this == obj) {
88+
return true;
89+
}
90+
if (!super.equals(obj)) {
91+
return false;
92+
}
93+
if (getClass() != obj.getClass()) {
94+
return false;
95+
}
96+
TestName other = (TestName) obj;
97+
return Objects.equals(displayName, other.displayName)
98+
&& Objects.equals(suiteName, other.suiteName)
99+
&& Objects.equals(className, other.className)
100+
&& Objects.equals(methodName, other.methodName)
101+
&& Objects.equals(parent, other.parent);
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
package ch.epfl.scala.bsp4j.extended;
5+
6+
import java.util.Objects;
7+
8+
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
9+
10+
import ch.epfl.scala.bsp4j.TestStart;
11+
12+
/**
13+
* Extended {@link TestStart}, which contains the Suite, class, method.
14+
* {@link TestStart} only contains file location which Gradle doesn't have.
15+
*/
16+
public class TestStartEx extends TestStart {
17+
18+
private TestName testName;
19+
20+
/**
21+
* Create a new instance of {@link TestStartEx}.
22+
*/
23+
public TestStartEx(@NonNull String displayName, @NonNull TestName testName) {
24+
super(displayName);
25+
this.testName = testName;
26+
}
27+
28+
public TestName getTestName() {
29+
return testName;
30+
}
31+
32+
public void setTestName(TestName testName) {
33+
this.testName = testName;
34+
}
35+
36+
@Override
37+
public int hashCode() {
38+
final int prime = 31;
39+
int result = super.hashCode();
40+
result = prime * result + Objects.hashCode(testName);
41+
return result;
42+
}
43+
44+
@Override
45+
public boolean equals(Object obj) {
46+
if (this == obj) {
47+
return true;
48+
}
49+
if (!super.equals(obj)) {
50+
return false;
51+
}
52+
if (getClass() != obj.getClass()) {
53+
return false;
54+
}
55+
TestStartEx other = (TestStartEx) obj;
56+
return Objects.equals(testName, other.testName);
57+
}
58+
}

extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildClient.java

+70
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import java.net.URI;
44
import java.text.SimpleDateFormat;
55
import java.util.Arrays;
6+
import java.util.Collections;
67
import java.util.Date;
78
import java.util.HashSet;
89
import java.util.LinkedHashSet;
10+
import java.util.LinkedList;
11+
import java.util.List;
912
import java.util.Objects;
1013
import java.util.Set;
1114
import java.util.concurrent.CompletableFuture;
@@ -23,6 +26,10 @@
2326
import org.eclipse.lsp4j.WorkDoneProgressEnd;
2427
import org.eclipse.lsp4j.WorkDoneProgressReport;
2528
import org.eclipse.lsp4j.jsonrpc.messages.Either;
29+
30+
import com.microsoft.gradle.bs.importer.model.JavaTestStatus;
31+
32+
import org.eclipse.jdt.ls.core.internal.JSONUtility;
2633
import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient;
2734

2835
import ch.epfl.scala.bsp4j.BuildClient;
@@ -38,6 +45,9 @@
3845
import ch.epfl.scala.bsp4j.TaskFinishParams;
3946
import ch.epfl.scala.bsp4j.TaskProgressParams;
4047
import ch.epfl.scala.bsp4j.TaskStartParams;
48+
import ch.epfl.scala.bsp4j.extended.TestFinishEx;
49+
import ch.epfl.scala.bsp4j.extended.TestName;
50+
import ch.epfl.scala.bsp4j.extended.TestStartEx;
4151

4252
public class GradleBuildClient implements BuildClient {
4353

@@ -133,6 +143,16 @@ public void onBuildTaskStart(TaskStartParams params) {
133143
Date now = new Date();
134144
String msg = "> Build starts at " + dateFormat.format(now) + "\n" + params.getMessage();
135145
lsClient.sendNotification(new ExecuteCommandParams(CLIENT_APPEND_BUILD_LOG_CMD, Arrays.asList(msg)));
146+
} else if (Objects.equals(params.getDataKind(), TaskDataKind.TEST_START)) {
147+
TestStartEx testStartEx = JSONUtility.toModel(params.getData(), TestStartEx.class);
148+
String displayName = testStartEx.getTestName().getDisplayName();
149+
if (displayName.matches("(?i)test suite '.+'") || displayName.matches("(?i)test\\s+\\w+\\(.*\\)\\(\\w+(\\.\\w+)*\\)")) {
150+
// ignore the suite start message as the display name.
151+
displayName = null;
152+
}
153+
String testIdentifier = getTestIdentifier(testStartEx.getTestName());
154+
lsClient.sendNotification(new ExecuteCommandParams("java.gradle.buildServer.onDidChangeTestItemStatus",
155+
Arrays.asList(testIdentifier, 2/*Running status*/, displayName)));
136156
} else {
137157
Either<String, Integer> id = Either.forLeft(params.getTaskId().getId());
138158
lsClient.createProgress(new WorkDoneProgressCreateParams(id));
@@ -165,6 +185,23 @@ public void onBuildTaskFinish(TaskFinishParams params) {
165185
if (params.getStatus() == StatusCode.ERROR) {
166186
failedTaskCache.addAll((params.getTaskId().getParents()));
167187
}
188+
} else if (Objects.equals(params.getDataKind(), TaskDataKind.TEST_FINISH)) {
189+
TestFinishEx testFinishEx = JSONUtility.toModel(params.getData(), TestFinishEx.class);
190+
String testIdentifier = getTestIdentifier(testFinishEx.getTestName());
191+
JavaTestStatus testStatus = switch (testFinishEx.getStatus()) {
192+
case PASSED -> JavaTestStatus.Passed;
193+
case FAILED -> JavaTestStatus.Failed;
194+
case IGNORED, CANCELLED, SKIPPED -> JavaTestStatus.Skipped;
195+
default -> null;
196+
};
197+
if (testStatus == null) {
198+
throw new IllegalArgumentException("Unsupported test status: " + testFinishEx.getStatus());
199+
}
200+
lsClient.sendNotification(new ExecuteCommandParams("java.gradle.buildServer.onDidChangeTestItemStatus",
201+
Arrays.asList(testIdentifier, testStatus.getValue(), null, testFinishEx.getStackTrace()))); // TODO: test duration is missing
202+
} else if (Objects.equals(params.getDataKind(), TaskDataKind.TEST_REPORT)) {
203+
lsClient.sendNotification(new ExecuteCommandParams("java.gradle.buildServer.onDidFinishTestRun",
204+
Arrays.asList(params.getTaskId().getId(), params.getMessage())));
168205
} else {
169206
Either<String, Integer> id = Either.forLeft(params.getTaskId().getId());
170207
WorkDoneProgressEnd workDoneProgressEnd = new WorkDoneProgressEnd();
@@ -174,6 +211,39 @@ public void onBuildTaskFinish(TaskFinishParams params) {
174211
}
175212
}
176213

214+
/**
215+
* Currently, the test name returned from gradle build server is started from the class name,
216+
* then follows the method or invocation name.
217+
* @return The test identifier
218+
*/
219+
private String getTestIdentifier(TestName testName) {
220+
List<String> testNames = new LinkedList<>();
221+
while (testName != null) {
222+
if (testName.getSuiteName() != null) {
223+
testNames.add(testName.getSuiteName());
224+
} else if (testName.getMethodName() != null) {
225+
testNames.add(testName.getMethodName());
226+
} else if (testName.getClassName() != null) {
227+
testNames.add(testName.getClassName());
228+
}
229+
testName = testName.getParent();
230+
}
231+
Collections.reverse(testNames);
232+
233+
// eliminate the common prefix when there is nested class test
234+
// only reserve the last one as the fully qualified name.
235+
int i = 0;
236+
for (; i < testNames.size() - 1; i++) {
237+
String cur = testNames.get(i);
238+
String next = testNames.get(i + 1);
239+
if (!next.startsWith(cur + "$")) {
240+
break;
241+
}
242+
}
243+
244+
return String.join("#", testNames.subList(i, testNames.size()));
245+
}
246+
177247
private class LruCache<T> extends LinkedHashSet<T> {
178248
private final int maxSize;
179249

0 commit comments

Comments
 (0)