Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extension/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ package.json
lib/
src/proto/
build/
src/java-test-runner.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@
<dynamicReference class="org.eclipse.jdt.internal.core.DynamicProjectReferences"/>
</builder>
</extension>
<extension point="org.eclipse.jdt.ls.core.delegateCommandHandler">
<delegateCommandHandler class="com.microsoft.gradle.bs.importer.handler.GradleDelegateCommandHandler">
<command id="java.gradle.delegateTest" />
</delegateCommandHandler>
</extension>
</plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

package ch.epfl.scala.bsp4j.extended;

import java.util.Objects;

import org.eclipse.lsp4j.jsonrpc.validation.NonNull;

import ch.epfl.scala.bsp4j.TestFinish;
import ch.epfl.scala.bsp4j.TestStatus;

/**
* Extended {@link TestFinish}, which contains the Suite, class, method.
* {@link TestFinish} only contains file location which Gradle doesn't have.
*/
public class TestFinishEx extends TestFinish {

private TestName testName;

private String stackTrace;

/**
* Create a new instance of {@link TestFinishEx}.
*/
public TestFinishEx(@NonNull String displayName, @NonNull TestStatus status,
@NonNull TestName testName) {
super(displayName, status);
this.testName = testName;
}


public TestName getTestName() {
return testName;
}

public void setTestName(TestName testName) {
this.testName = testName;
}

public String getStackTrace() {
return stackTrace;
}

public void setStackTrace(String stackTrace) {
this.stackTrace = stackTrace;
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(testName, stackTrace);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TestFinishEx other = (TestFinishEx) obj;
return Objects.equals(testName, other.testName)
&& Objects.equals(stackTrace, other.stackTrace);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

package ch.epfl.scala.bsp4j.extended;

import java.util.Objects;

import org.eclipse.lsp4j.jsonrpc.validation.NonNull;

/**
* BSP TestName, which contains the test name and the test hierarchy
* e.g. method/class/suite
*/
public class TestName {

private String displayName;

private String suiteName;

private String className;

private String methodName;

private TestName parent;

/**
* Create a new instance of {@link TestName}.
*/
public TestName(@NonNull String displayName, String suiteName,
String className, String methodName) {
this.displayName = displayName;
this.suiteName = suiteName;
this.className = className;
this.methodName = methodName;
}

public String getDisplayName() {
return displayName;
}

public void setDisplayName(String displayName) {
this.displayName = displayName;
}

public String getSuiteName() {
return suiteName;
}

public void setSuiteName(String suiteName) {
this.suiteName = suiteName;
}

public String getClassName() {
return className;
}

public void setClassName(String className) {
this.className = className;
}

public String getMethodName() {
return methodName;
}

public void setMethodName(String methodName) {
this.methodName = methodName;
}

public TestName getParent() {
return parent;
}

public void setParent(TestName parent) {
this.parent = parent;
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(displayName, suiteName, className, methodName, parent);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TestName other = (TestName) obj;
return Objects.equals(displayName, other.displayName)
&& Objects.equals(suiteName, other.suiteName)
&& Objects.equals(className, other.className)
&& Objects.equals(methodName, other.methodName)
&& Objects.equals(parent, other.parent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

package ch.epfl.scala.bsp4j.extended;

import java.util.Objects;

import org.eclipse.lsp4j.jsonrpc.validation.NonNull;

import ch.epfl.scala.bsp4j.TestStart;

/**
* Extended {@link TestStart}, which contains the Suite, class, method.
* {@link TestStart} only contains file location which Gradle doesn't have.
*/
public class TestStartEx extends TestStart {

private TestName testName;

/**
* Create a new instance of {@link TestStartEx}.
*/
public TestStartEx(@NonNull String displayName, @NonNull TestName testName) {
super(displayName);
this.testName = testName;
}

public TestName getTestName() {
return testName;
}

public void setTestName(TestName testName) {
this.testName = testName;
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hashCode(testName);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TestStartEx other = (TestStartEx) obj;
return Objects.equals(testName, other.testName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
Expand All @@ -23,6 +26,10 @@
import org.eclipse.lsp4j.WorkDoneProgressEnd;
import org.eclipse.lsp4j.WorkDoneProgressReport;
import org.eclipse.lsp4j.jsonrpc.messages.Either;

import com.microsoft.gradle.bs.importer.model.JavaTestStatus;

import org.eclipse.jdt.ls.core.internal.JSONUtility;
import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient;

import ch.epfl.scala.bsp4j.BuildClient;
Expand All @@ -38,6 +45,9 @@
import ch.epfl.scala.bsp4j.TaskFinishParams;
import ch.epfl.scala.bsp4j.TaskProgressParams;
import ch.epfl.scala.bsp4j.TaskStartParams;
import ch.epfl.scala.bsp4j.extended.TestFinishEx;
import ch.epfl.scala.bsp4j.extended.TestName;
import ch.epfl.scala.bsp4j.extended.TestStartEx;

public class GradleBuildClient implements BuildClient {

Expand Down Expand Up @@ -133,6 +143,16 @@ public void onBuildTaskStart(TaskStartParams params) {
Date now = new Date();
String msg = "> Build starts at " + dateFormat.format(now) + "\n" + params.getMessage();
lsClient.sendNotification(new ExecuteCommandParams(CLIENT_APPEND_BUILD_LOG_CMD, Arrays.asList(msg)));
} else if (Objects.equals(params.getDataKind(), TaskDataKind.TEST_START)) {
TestStartEx testStartEx = JSONUtility.toModel(params.getData(), TestStartEx.class);
String displayName = testStartEx.getTestName().getDisplayName();
if (displayName.matches("(?i)test suite '.+'") || displayName.matches("(?i)test\\s+\\w+\\(.*\\)\\(\\w+(\\.\\w+)*\\)")) {
// ignore the suite start message as the display name.
displayName = null;
}
List<String> testParts = getTestParts(testStartEx.getTestName());
lsClient.sendNotification(new ExecuteCommandParams("java.gradle.buildServer.onDidChangeTestItemStatus",
Arrays.asList(testParts, 2/*Running status*/, displayName)));
} else {
Either<String, Integer> id = Either.forLeft(params.getTaskId().getId());
lsClient.createProgress(new WorkDoneProgressCreateParams(id));
Expand Down Expand Up @@ -165,6 +185,23 @@ public void onBuildTaskFinish(TaskFinishParams params) {
if (params.getStatus() == StatusCode.ERROR) {
failedTaskCache.addAll((params.getTaskId().getParents()));
}
} else if (Objects.equals(params.getDataKind(), TaskDataKind.TEST_FINISH)) {
TestFinishEx testFinishEx = JSONUtility.toModel(params.getData(), TestFinishEx.class);
List<String> testParts = getTestParts(testFinishEx.getTestName());
JavaTestStatus testStatus = switch (testFinishEx.getStatus()) {
case PASSED -> JavaTestStatus.Passed;
case FAILED -> JavaTestStatus.Failed;
case IGNORED, CANCELLED, SKIPPED -> JavaTestStatus.Skipped;
default -> null;
};
if (testStatus == null) {
throw new IllegalArgumentException("Unsupported test status: " + testFinishEx.getStatus());
}
lsClient.sendNotification(new ExecuteCommandParams("java.gradle.buildServer.onDidChangeTestItemStatus",
Arrays.asList(testParts, testStatus.getValue(), null, testFinishEx.getStackTrace()))); // TODO: test duration is missing
} else if (Objects.equals(params.getDataKind(), TaskDataKind.TEST_REPORT)) {
lsClient.sendNotification(new ExecuteCommandParams("java.gradle.buildServer.onDidFinishTestRun",
Arrays.asList(params.getTaskId().getId(), params.getMessage())));
} else {
Either<String, Integer> id = Either.forLeft(params.getTaskId().getId());
WorkDoneProgressEnd workDoneProgressEnd = new WorkDoneProgressEnd();
Expand All @@ -174,6 +211,39 @@ public void onBuildTaskFinish(TaskFinishParams params) {
}
}

/**
* Currently, the test name returned from gradle build server is started from the class name,
* then follows the method or invocation name.
* @return The test identifier parts
*/
private List<String> getTestParts(TestName testName) {
List<String> testNames = new LinkedList<>();
while (testName != null) {
if (testName.getSuiteName() != null) {
testNames.add(testName.getSuiteName());
} else if (testName.getMethodName() != null) {
testNames.add(testName.getMethodName());
} else if (testName.getClassName() != null) {
testNames.add(testName.getClassName());
}
testName = testName.getParent();
}
Collections.reverse(testNames);

// eliminate the common prefix when there is nested class test
// only reserve the last one as the fully qualified name.
int i = 0;
for (; i < testNames.size() - 1; i++) {
String cur = testNames.get(i);
String next = testNames.get(i + 1);
if (!next.startsWith(cur + "$")) {
break;
}
}

return testNames.subList(i, testNames.size());
}

private class LruCache<T> extends LinkedHashSet<T> {
private final int maxSize;

Expand Down
Loading