Skip to content
Open
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
10 changes: 9 additions & 1 deletion extension/src/client/TaskServerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
GradleBuild,
Environment,
GradleConfig,
GradleTestEvent,
RunBuildRequest,
RunBuildReply,
CancelBuildRequest,
Expand Down Expand Up @@ -230,7 +231,8 @@ export class TaskServerClient implements vscode.Disposable {
showOutputColors = true,
additionalToolOptions = "",
title?: string,
location?: vscode.ProgressLocation
location?: vscode.ProgressLocation,
onTestEvent?: (event: GradleTestEvent) => void
): Promise<void> {
await this.connectWaiter.wait();
this.statusBarItem.hide();
Expand Down Expand Up @@ -260,6 +262,7 @@ export class TaskServerClient implements vscode.Disposable {
request.setJavaDebugPort(javaDebugPort);
request.setInput(input);
request.setAdditionalToolOptions(additionalToolOptions);
request.setStreamTestEvents(Boolean(onTestEvent));

if (javaDebugPort > 0) {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(projectFolder));
Expand All @@ -285,6 +288,11 @@ export class TaskServerClient implements vscode.Disposable {
case RunBuildReply.KindCase.CANCELLED:
this.handleRunBuildCancelled(args, runBuildReply.getCancelled()!, task);
break;
case RunBuildReply.KindCase.TEST_EVENT:
if (onTestEvent) {
onTestEvent(runBuildReply.getTestEvent()!);
}
break;
}
})
.on("error", reject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ public class GradleBuildRunner {
private ProgressListener progressListener;
private Boolean javaDebugCleanOutputCache;
private String additionalToolOptions;
private Boolean streamTestEvents;

public GradleBuildRunner(String projectDir, List<String> args, GradleConfig gradleConfig, String cancellationKey,
Boolean colorOutput, int javaDebugPort, Boolean javaDebugCleanOutputCache, String additionalToolOptions) {
Boolean colorOutput, int javaDebugPort, Boolean javaDebugCleanOutputCache, String additionalToolOptions,
Boolean streamTestEvents) {
this.projectDir = projectDir;
this.args = args;
this.gradleConfig = gradleConfig;
Expand All @@ -90,6 +92,13 @@ public GradleBuildRunner(String projectDir, List<String> args, GradleConfig grad
this.javaDebugPort = javaDebugPort;
this.javaDebugCleanOutputCache = javaDebugCleanOutputCache;
this.additionalToolOptions = additionalToolOptions;
this.streamTestEvents = streamTestEvents;
}

public GradleBuildRunner(String projectDir, List<String> args, GradleConfig gradleConfig, String cancellationKey,
Boolean colorOutput, int javaDebugPort, Boolean javaDebugCleanOutputCache, String additionalToolOptions) {
this(projectDir, args, gradleConfig, cancellationKey, colorOutput, javaDebugPort, javaDebugCleanOutputCache,
additionalToolOptions, false);
}

public GradleBuildRunner(String projectDir, List<String> args, GradleConfig gradleConfig, String cancellationKey) {
Expand Down Expand Up @@ -133,6 +142,9 @@ private void runBuild(ProjectConnection connection) throws GradleBuildRunnerExce
progressEvents.add(OperationType.PROJECT_CONFIGURATION);
progressEvents.add(OperationType.TASK);
progressEvents.add(OperationType.TRANSFORM);
if (Boolean.TRUE.equals(streamTestEvents)) {
progressEvents.add(OperationType.TEST);
}

CancellationToken cancellationToken = GradleBuildCancellation.buildToken(cancellationKey);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.github.badsyntax.gradle.Cancelled;
import com.github.badsyntax.gradle.ErrorMessageBuilder;
import com.github.badsyntax.gradle.GradleBuildRunner;
import com.github.badsyntax.gradle.GradleTestEvent;
import com.github.badsyntax.gradle.Output;
import com.github.badsyntax.gradle.Progress;
import com.github.badsyntax.gradle.RunBuildReply;
Expand All @@ -15,11 +16,25 @@
import io.grpc.stub.StreamObserver;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.gradle.tooling.BuildCancelledException;
import org.gradle.tooling.BuildException;
import org.gradle.tooling.Failure;
import org.gradle.tooling.UnsupportedVersionException;
import org.gradle.tooling.events.ProgressEvent;
import org.gradle.tooling.events.ProgressListener;
import org.gradle.tooling.events.test.Destination;
import org.gradle.tooling.events.test.JvmTestOperationDescriptor;
import org.gradle.tooling.events.test.TestFailureResult;
import org.gradle.tooling.events.test.TestFinishEvent;
import org.gradle.tooling.events.test.TestOperationDescriptor;
import org.gradle.tooling.events.test.TestOutputDescriptor;
import org.gradle.tooling.events.test.TestOutputEvent;
import org.gradle.tooling.events.test.TestSkippedResult;
import org.gradle.tooling.events.test.TestStartEvent;
import org.gradle.tooling.events.test.TestSuccessResult;
import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -32,37 +47,36 @@ public class RunBuildHandler {
private ProgressListener progressListener;
private ByteBufferOutputStream standardOutputListener;
private ByteBufferOutputStream standardErrorListener;
private final Object responseLock = new Object();

public RunBuildHandler(RunBuildRequest req, StreamObserver<RunBuildReply> responseObserver) {
this.req = req;
this.responseObserver = responseObserver;
this.progressListener = (ProgressEvent event) -> {
synchronized (RunBuildHandler.class) {
if (req.getStreamTestEvents() && isTestEvent(event)) {
replyWithTestEvent(event);
} else {
replyWithProgress(event);
}
};
this.standardOutputListener = new ByteBufferOutputStream() {
@Override
public void onFlush(byte[] bytes) {
synchronized (RunBuildHandler.class) {
replyWithStandardOutput(bytes);
}
replyWithStandardOutput(bytes);
}
};
this.standardErrorListener = new ByteBufferOutputStream() {
@Override
public void onFlush(byte[] bytes) {
synchronized (RunBuildHandler.class) {
replyWithStandardError(bytes);
}
replyWithStandardError(bytes);
}
};
}

public void run() {
GradleBuildRunner gradleRunner = new GradleBuildRunner(req.getProjectDir(), req.getArgsList(),
req.getGradleConfig(), req.getCancellationKey(), req.getShowOutputColors(), req.getJavaDebugPort(),
req.getJavaDebugCleanOutputCache(), req.getAdditionalToolOptions());
req.getJavaDebugCleanOutputCache(), req.getAdditionalToolOptions(), req.getStreamTestEvents());
gradleRunner.setProgressListener(progressListener).setStandardOutputStream(standardOutputListener)
.setStandardErrorStream(standardErrorListener);

Expand All @@ -73,10 +87,10 @@ public void run() {
try {
gradleRunner.run();
replyWithSuccess();
responseObserver.onCompleted();
completeResponse();
} catch (BuildCancelledException e) {
replyWithCancelled(e);
responseObserver.onCompleted();
completeResponse();
} catch (BuildException | UnsupportedVersionException | UnsupportedBuildArgumentException
| IllegalStateException | IOException | GradleBuildRunnerException e) {
logger.error(e.getMessage());
Expand All @@ -85,36 +99,152 @@ public void run() {
}

public void replyWithCancelled(BuildCancelledException e) {
responseObserver.onNext(RunBuildReply.newBuilder()
sendReply(RunBuildReply.newBuilder()
.setCancelled(Cancelled.newBuilder().setMessage(e.getMessage()).setProjectDir(req.getProjectDir()))
.build());
}

public void replyWithError(Exception e) {
responseObserver.onError(ErrorMessageBuilder.build(e));
synchronized (responseLock) {
responseObserver.onError(ErrorMessageBuilder.build(e));
}
}

public void replyWithSuccess() {
responseObserver.onNext(RunBuildReply.newBuilder()
sendReply(RunBuildReply.newBuilder()
.setRunBuildResult(RunBuildResult.newBuilder().setMessage("Successfully run build")).build());
}

private void replyWithProgress(ProgressEvent progressEvent) {
responseObserver.onNext(RunBuildReply.newBuilder()
sendReply(RunBuildReply.newBuilder()
.setProgress(Progress.newBuilder().setMessage(progressEvent.getDisplayName())).build());
}

private void replyWithTestEvent(ProgressEvent progressEvent) {
sendReply(RunBuildReply.newBuilder().setTestEvent(convertTestEvent(progressEvent)).build());
}

private void sendReply(RunBuildReply reply) {
synchronized (responseLock) {
responseObserver.onNext(reply);
}
}

private void completeResponse() {
synchronized (responseLock) {
responseObserver.onCompleted();
}
}

private static boolean isTestEvent(ProgressEvent event) {
return event instanceof TestStartEvent || event instanceof TestFinishEvent || event instanceof TestOutputEvent;
}

private static GradleTestEvent convertTestEvent(ProgressEvent event) {
GradleTestEvent.Builder builder = GradleTestEvent.newBuilder().setEventTime(event.getEventTime())
.setDisplayName(event.getDisplayName());

if (event instanceof TestOutputEvent) {
TestOutputDescriptor descriptor = ((TestOutputEvent) event).getDescriptor();
fillDescriptor(builder, descriptor);
builder.setEventType(GradleTestEvent.EventType.OUTPUT).setMessage(descriptor.getMessage());
if (Destination.StdOut.equals(descriptor.getDestination())) {
builder.setOutputDestination(GradleTestEvent.OutputDestination.STDOUT);
} else if (Destination.StdErr.equals(descriptor.getDestination())) {
builder.setOutputDestination(GradleTestEvent.OutputDestination.STDERR);
}
return builder.build();
}

if (event instanceof TestStartEvent) {
builder.setEventType(GradleTestEvent.EventType.STARTED);
} else if (event instanceof TestFinishEvent) {
TestFinishEvent finishEvent = (TestFinishEvent) event;
if (finishEvent.getResult() instanceof TestSuccessResult) {
builder.setEventType(GradleTestEvent.EventType.SUCCEEDED);
} else if (finishEvent.getResult() instanceof TestSkippedResult) {
builder.setEventType(GradleTestEvent.EventType.SKIPPED);
} else if (finishEvent.getResult() instanceof TestFailureResult) {
builder.setEventType(GradleTestEvent.EventType.FAILED);
String failureMessage = failureMessage((TestFailureResult) finishEvent.getResult());
if (!failureMessage.isEmpty()) {
builder.setMessage(failureMessage);
}
}
}

fillDescriptor(builder, ((org.gradle.tooling.events.test.TestProgressEvent) event).getDescriptor());
return builder.build();
}

private static void fillDescriptor(GradleTestEvent.Builder builder,
org.gradle.tooling.events.OperationDescriptor descriptor) {
builder.setId(descriptorPath(descriptor)).setName(descriptor.getName())
.setDisplayName(descriptor.getDisplayName());
if (descriptor.getParent() != null) {
builder.setParentId(descriptorPath(descriptor.getParent()));
}
if (descriptor instanceof TestOperationDescriptor) {
builder.setDisplayName(((TestOperationDescriptor) descriptor).getTestDisplayName());
}
if (descriptor instanceof JvmTestOperationDescriptor) {
JvmTestOperationDescriptor jvmDescriptor = (JvmTestOperationDescriptor) descriptor;
if (jvmDescriptor.getClassName() != null) {
builder.setClassName(jvmDescriptor.getClassName());
}
if (jvmDescriptor.getMethodName() != null) {
builder.setMethodName(jvmDescriptor.getMethodName());
}
if (jvmDescriptor.getSuiteName() != null) {
builder.setSuiteName(jvmDescriptor.getSuiteName());
}
}
}

private static String descriptorPath(org.gradle.tooling.events.OperationDescriptor descriptor) {
List<String> names = new ArrayList<>();
org.gradle.tooling.events.OperationDescriptor current = descriptor;
while (current != null) {
names.add(current.getName());
current = current.getParent();
}
Collections.reverse(names);
return String.join("/", names);
}

private void replyWithStandardOutput(byte[] bytes) {
ByteString byteString = ByteString.copyFrom(bytes);
responseObserver.onNext(RunBuildReply.newBuilder()
sendReply(RunBuildReply.newBuilder()
.setOutput(Output.newBuilder().setOutputType(Output.OutputType.STDOUT).setOutputBytes(byteString))
.build());
}

private void replyWithStandardError(byte[] bytes) {
ByteString byteString = ByteString.copyFrom(bytes);
responseObserver.onNext(RunBuildReply.newBuilder()
sendReply(RunBuildReply.newBuilder()
.setOutput(Output.newBuilder().setOutputType(Output.OutputType.STDERR).setOutputBytes(byteString))
.build());
}

private static String failureMessage(TestFailureResult failureResult) {
List<String> messages = new ArrayList<>();
for (Failure failure : failureResult.getFailures()) {
collectFailureMessages(failure, messages);
}
return String.join("\n---\n", messages);
}

private static void collectFailureMessages(Failure failure, List<String> messages) {
String message = failure.getMessage();
String description = failure.getDescription();
if (!Strings.isNullOrEmpty(message)) {
messages.add(message);
}
if (!Strings.isNullOrEmpty(description) && !description.equals(message)) {
messages.add(description);
}
for (Failure cause : failure.getCauses()) {
collectFailureMessages(cause, messages);
}
}
}
Loading
Loading