3
3
import java .net .URI ;
4
4
import java .text .SimpleDateFormat ;
5
5
import java .util .Arrays ;
6
+ import java .util .Collections ;
6
7
import java .util .Date ;
7
8
import java .util .HashSet ;
8
9
import java .util .LinkedHashSet ;
10
+ import java .util .LinkedList ;
11
+ import java .util .List ;
9
12
import java .util .Objects ;
10
13
import java .util .Set ;
11
14
import java .util .concurrent .CompletableFuture ;
23
26
import org .eclipse .lsp4j .WorkDoneProgressEnd ;
24
27
import org .eclipse .lsp4j .WorkDoneProgressReport ;
25
28
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 ;
26
33
import org .eclipse .jdt .ls .core .internal .JavaClientConnection .JavaLanguageClient ;
27
34
28
35
import ch .epfl .scala .bsp4j .BuildClient ;
38
45
import ch .epfl .scala .bsp4j .TaskFinishParams ;
39
46
import ch .epfl .scala .bsp4j .TaskProgressParams ;
40
47
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 ;
41
51
42
52
public class GradleBuildClient implements BuildClient {
43
53
@@ -133,6 +143,16 @@ public void onBuildTaskStart(TaskStartParams params) {
133
143
Date now = new Date ();
134
144
String msg = "> Build starts at " + dateFormat .format (now ) + "\n " + params .getMessage ();
135
145
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 )));
136
156
} else {
137
157
Either <String , Integer > id = Either .forLeft (params .getTaskId ().getId ());
138
158
lsClient .createProgress (new WorkDoneProgressCreateParams (id ));
@@ -165,6 +185,23 @@ public void onBuildTaskFinish(TaskFinishParams params) {
165
185
if (params .getStatus () == StatusCode .ERROR ) {
166
186
failedTaskCache .addAll ((params .getTaskId ().getParents ()));
167
187
}
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 ())));
168
205
} else {
169
206
Either <String , Integer > id = Either .forLeft (params .getTaskId ().getId ());
170
207
WorkDoneProgressEnd workDoneProgressEnd = new WorkDoneProgressEnd ();
@@ -174,6 +211,39 @@ public void onBuildTaskFinish(TaskFinishParams params) {
174
211
}
175
212
}
176
213
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
+
177
247
private class LruCache <T > extends LinkedHashSet <T > {
178
248
private final int maxSize ;
179
249
0 commit comments