Skip to content

Commit

Permalink
Add support for running tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthur McGibbon committed May 30, 2024
1 parent 77ac9e4 commit 5462e49
Show file tree
Hide file tree
Showing 19 changed files with 1,038 additions and 73 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Following BSP requests are supported in the current implementation:
- `buildTarget/cleanCache`
- `buildTarget/javacOptions`
- `buildTarget/scalacOptions`
- `buildTarget/test`
- `workspace/buildTargets`
- `workspace/reload`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ public Object buildAll(String modelName, Project rootProject) {
gradleSourceSet.setCleanTaskName(cleanTaskName);
Set<String> taskNames = new HashSet<>();
gradleSourceSet.setTaskNames(taskNames);
taskNames.add(classesTaskName);
taskNames.add(cleanTaskName);
taskNames.add(getFullTaskName(projectPath, sourceSet.getProcessResourcesTaskName()));
String projectName = stripPathPrefix(gradleSourceSet.getProjectPath());
if (projectName == null || projectName.length() == 0) {
projectName = gradleSourceSet.getProjectName();
Expand Down
108 changes: 108 additions & 0 deletions server/src/main/java/ch/epfl/scala/bsp4j/extended/TestFinishEx.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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 org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;

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 String suiteName;

private String className;

private String methodName;

private String stackTrace;

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

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 String getStackTrace() {
return stackTrace;
}

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

@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add(super.toString());
b.add("suiteName", this.suiteName);
b.add("className", this.className);
b.add("methodName", this.methodName);
return b.toString();
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(suiteName, className, methodName, 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(suiteName, other.suiteName)
&& Objects.equals(className, other.className)
&& Objects.equals(methodName, other.methodName)
&& Objects.equals(stackTrace, other.stackTrace);
}
}
96 changes: 96 additions & 0 deletions server/src/main/java/ch/epfl/scala/bsp4j/extended/TestStartEx.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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 org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;

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 String suiteName;

private String className;

private String methodName;

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

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;
}

@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add(super.toString());
b.add("suiteName", this.suiteName);
b.add("className", this.className);
b.add("methodName", this.methodName);
return b.toString();
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(suiteName, className, methodName);
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(suiteName, other.suiteName)
&& Objects.equals(className, other.className)
&& Objects.equals(methodName, other.methodName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,32 @@
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.gradle.tooling.BuildException;
import org.gradle.tooling.BuildLauncher;
import org.gradle.tooling.GradleConnectionException;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ModelBuilder;
import org.gradle.tooling.ProjectConnection;
import org.gradle.tooling.TestLauncher;
import org.gradle.tooling.events.OperationType;
import org.gradle.tooling.model.build.BuildEnvironment;
import org.gradle.util.GradleVersion;

import com.microsoft.java.bs.core.internal.managers.PreferenceManager;
import com.microsoft.java.bs.core.internal.reporter.CompileProgressReporter;
import com.microsoft.java.bs.core.internal.reporter.DefaultProgressReporter;
import com.microsoft.java.bs.core.internal.reporter.ProgressReporter;
import com.microsoft.java.bs.core.internal.reporter.TestReportReporter;
import com.microsoft.java.bs.gradle.model.GradleSourceSets;
import com.microsoft.java.bs.gradle.model.impl.DefaultGradleSourceSets;

import ch.epfl.scala.bsp4j.BuildClient;
import ch.epfl.scala.bsp4j.BuildTargetIdentifier;
import ch.epfl.scala.bsp4j.StatusCode;

/**
Expand All @@ -47,17 +55,21 @@ public GradleApiConnector(PreferenceManager preferenceManager) {
*/
public String getGradleVersion(URI projectUri) {
try (ProjectConnection connection = getGradleConnector(projectUri).connect()) {
BuildEnvironment model = connection
.model(BuildEnvironment.class)
.withArguments("--no-daemon")
.get();
return model.getGradle().getGradleVersion();
return getGradleVersion(connection);
} catch (BuildException e) {
LOGGER.severe("Failed to get Gradle version: " + e.getMessage());
return "";
}
}

private String getGradleVersion(ProjectConnection connection) {
BuildEnvironment model = connection
.model(BuildEnvironment.class)
.withArguments("--no-daemon")
.get();
return model.getGradle().getGradleVersion();
}

/**
* Get the source sets of the Gradle project.
*
Expand Down Expand Up @@ -138,6 +150,77 @@ public StatusCode runTasks(URI projectUri, ProgressReporter reporter, String...
return statusCode;
}

/**
* request Gradle to run tests.
*/
public StatusCode runTests(URI projectUri,
Map<BuildTargetIdentifier, Map<String, Set<String>>> testClassesMethodsMap,
List<String> jvmOptions,
List<String> args,
Map<String, String> envVars,
BuildClient client, String originId,
CompileProgressReporter compileProgressReporter) {

StatusCode statusCode = StatusCode.OK;
ProgressReporter reporter = new DefaultProgressReporter(client);
try (ProjectConnection connection = getGradleConnector(projectUri).connect()) {
String gradleVersion = getGradleVersion(connection);
if (GradleVersion.version(gradleVersion).compareTo(GradleVersion.version("2.6")) < 0) {
reporter.sendError("Error running test classes: Gradle version "
+ gradleVersion + " must be >= 2.6");
} else if (envVars != null && !envVars.isEmpty()
&& GradleVersion.version(gradleVersion).compareTo(GradleVersion.version("3.5")) < 0) {
reporter.sendError("Error running test classes With Environment Variables: Gradle version "
+ gradleVersion + " must be >= 3.5");
} else {
for (Map.Entry<BuildTargetIdentifier, Map<String, Set<String>>> entry :
testClassesMethodsMap.entrySet()) {
TestReportReporter testReportReporter = new TestReportReporter(entry.getKey(),
client, originId);
final ByteArrayOutputStream errorOut = new ByteArrayOutputStream();
try (errorOut) {
TestLauncher launcher = Utils
.getTestLauncher(connection, preferenceManager.getPreferences())
.setStandardError(errorOut)
.addProgressListener(testReportReporter, OperationType.TEST);
if (compileProgressReporter != null) {
launcher.addProgressListener(compileProgressReporter, OperationType.TASK);
}
for (Map.Entry<String, Set<String>> classesMethods : entry.getValue().entrySet()) {
if (classesMethods.getValue() != null && !classesMethods.getValue().isEmpty()) {
launcher.withJvmTestMethods(classesMethods.getKey(), classesMethods.getValue());
} else {
launcher.withJvmTestClasses(classesMethods.getKey());
}
}
launcher.withArguments(args);
launcher.setJvmArguments(jvmOptions);
// env vars requires Gradle >= 3.5
launcher.setEnvironmentVariables(envVars);
launcher.run();
} catch (IOException e) {
// caused by close the output stream, just simply log the error.
LOGGER.severe(e.getMessage());
} catch (GradleConnectionException | IllegalStateException e) {
String message = String.join("\n", ExceptionUtils.getRootCauseStackTraceList(e));
if (errorOut.size() > 0) {
message = message + '\n' + errorOut;
}
testReportReporter.addException(message);
statusCode = StatusCode.ERROR;
} finally {
testReportReporter.sendResult();
}
}
}
} catch (GradleConnectionException | IllegalStateException e) {
reporter.sendError("Error running test classes: " + e.getMessage());
statusCode = StatusCode.ERROR;
}

return statusCode;
}

public void shutdown() {
connectors.values().forEach(GradleConnector::disconnect);
}
Expand Down
Loading

0 comments on commit 5462e49

Please sign in to comment.