From 3b5737c42a7b54a4a04c8abd02d63b74c97cb09b Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 6 Jan 2025 10:49:04 +0530 Subject: [PATCH 1/7] Improve fast-run feature to fail-fast on compilation errors --- .../commons/workspace/RunResult.java | 32 ++++++++++ .../commons/workspace/WorkspaceManager.java | 4 +- .../command/executors/RunExecutor.java | 24 +++++++- .../workspace/BallerinaWorkspaceManager.java | 59 +++++++++---------- 4 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java new file mode 100644 index 000000000000..e7107f9889df --- /dev/null +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ballerinalang.langserver.commons.workspace; + +import io.ballerina.tools.diagnostics.Diagnostic; + +import java.util.Collection; + +/** + * Represents the result of a run operation. + * + * @since 2201.12.0 + */ +public record RunResult(Process process, Collection diagnostics) { + +} diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java index 1a6206fb9c01..63c7a64bc87b 100644 --- a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java @@ -219,7 +219,7 @@ public interface WorkspaceManager { * @throws WorkspaceDocumentException when project or document not found */ void didChangeWatched(Path filePath, FileEvent fileEvent) throws WorkspaceDocumentException; - + /** * The file change notification is sent from the client to the server to signal changes to watched files. * @@ -245,7 +245,7 @@ public interface WorkspaceManager { * @throws IOException If failed to start the process. * @since 2201.6.0 */ - Optional run(RunContext runContext) throws IOException; + RunResult run(RunContext runContext) throws IOException; /** * Stop a running process started with {@link #run}. diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index deadb291f6a3..ad4c957bc135 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -19,6 +19,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; import org.ballerinalang.annotation.JavaSPIService; import org.ballerinalang.langserver.commons.ExecuteCommandContext; import org.ballerinalang.langserver.commons.client.ExtendedLanguageClient; @@ -26,6 +28,7 @@ import org.ballerinalang.langserver.commons.command.LSCommandExecutorException; import org.ballerinalang.langserver.commons.command.spi.LSCommandExecutor; import org.ballerinalang.langserver.commons.workspace.RunContext; +import org.ballerinalang.langserver.commons.workspace.RunResult; import org.eclipse.lsp4j.LogTraceParams; import java.io.IOException; @@ -34,10 +37,12 @@ import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.StreamSupport; @@ -67,11 +72,24 @@ public class RunExecutor implements LSCommandExecutor { public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorException { try { RunContext workspaceRunContext = getWorkspaceRunContext(context); - Optional processOpt = context.workspace().run(workspaceRunContext); - if (processOpt.isEmpty()) { + RunResult runResult = context.workspace().run(workspaceRunContext); + + Collection diagnostics = runResult.diagnostics(); + for (Diagnostic diagnostic : diagnostics) { + LogTraceParams diagnosticMessage = new LogTraceParams(diagnostic.toString(), ERROR_CHANNEL); + context.getLanguageClient().logTrace(diagnosticMessage); + } + if (diagnostics.stream().anyMatch(diagnostic -> diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR)) { + LogTraceParams error = new LogTraceParams("error: compilation contains errors", ERROR_CHANNEL); + context.getLanguageClient().logTrace(error); + return false; + } + + Process process = runResult.process(); + if (Objects.isNull(process)) { return false; } - Process process = processOpt.get(); + listenOutputAsync(context.getLanguageClient(), process::getInputStream, OUT_CHANNEL); listenOutputAsync(context.getLanguageClient(), process::getErrorStream, ERROR_CHANNEL); return true; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index bf6d6aeea454..0640044a4b0a 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -61,6 +61,7 @@ import org.ballerinalang.langserver.commons.eventsync.EventKind; import org.ballerinalang.langserver.commons.eventsync.exceptions.EventSyncException; import org.ballerinalang.langserver.commons.workspace.RunContext; +import org.ballerinalang.langserver.commons.workspace.RunResult; import org.ballerinalang.langserver.commons.workspace.WorkspaceDocumentException; import org.ballerinalang.langserver.commons.workspace.WorkspaceDocumentManager; import org.ballerinalang.langserver.commons.workspace.WorkspaceManager; @@ -92,6 +93,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -102,7 +104,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -595,18 +596,39 @@ public String uriScheme() { } @Override - public Optional run(RunContext executionContext) throws IOException { + public RunResult run(RunContext executionContext) throws IOException { Path projectRoot = projectRoot(executionContext.balSourcePath()); Optional projectContext = validateProjectContext(projectRoot); if (projectContext.isEmpty()) { - return Optional.empty(); + return new RunResult(null, Collections.emptyList()); } - if (!prepareProjectForExecution(projectContext.get())) { - return Optional.empty(); + if (!stopProject(projectContext.get())) { + logError("Run command execution aborted because couldn't stop the previous run"); + return new RunResult(null, Collections.emptyList()); } - return executeProject(projectContext.get(), executionContext); + Project project = projectContext.get().project(); + Optional packageCompilation = waitAndGetPackageCompilation(project.sourceRoot(), true); + if (packageCompilation.isEmpty()) { + logError("Run command execution aborted because package compilation failed"); + return new RunResult(null, Collections.emptyList()); + } + + JBallerinaBackend jBallerinaBackend = execBackend(projectContext.get(), packageCompilation.get()); + Collection diagnostics = new LinkedList<>(); + // check for compilation errors + diagnostics.addAll(jBallerinaBackend.diagnosticResult().diagnostics(false)); + // Add tool resolution diagnostics to diagnostics + diagnostics.addAll(project.currentPackage().getBuildToolResolution().getDiagnosticList()); + + if (diagnostics.stream().anyMatch(diagnostic -> diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR)) { + return new RunResult(null, diagnostics); + } + + Optional process = executeProject(projectContext.get(), executionContext); + return process.map(value -> new RunResult(value, diagnostics)) + .orElseGet(() -> new RunResult(null, diagnostics)); } private Optional validateProjectContext(Path projectRoot) { @@ -619,31 +641,6 @@ private Optional validateProjectContext(Path projectRoot) { return projectContextOpt; } - private boolean prepareProjectForExecution(ProjectContext projectContext) { - // stop previous project run - if (!stopProject(projectContext)) { - logError("Run command execution aborted because couldn't stop the previous run"); - return false; - } - - Project project = projectContext.project(); - Optional packageCompilation = waitAndGetPackageCompilation(project.sourceRoot(), true); - if (packageCompilation.isEmpty()) { - logError("Run command execution aborted because package compilation failed"); - return false; - } - - // check for compilation errors - JBallerinaBackend jBallerinaBackend = execBackend(projectContext, packageCompilation.get()); - Collection diagnostics = jBallerinaBackend.diagnosticResult().diagnostics(false); - if (diagnostics.stream().anyMatch(BallerinaWorkspaceManager::isError)) { - logError("Run command execution aborted due to compilation errors: " + diagnostics); - return false; - } - - return true; - } - private Optional executeProject(ProjectContext projectContext, RunContext context) throws IOException { Project project = projectContext.project(); Package pkg = project.currentPackage(); From 68d93dfd066ab7ef94a48a1121724c85653565ac Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 6 Jan 2025 10:56:11 +0530 Subject: [PATCH 2/7] Modify debug server to terminate upon restart failures --- .../debugadapter/JBallerinaDebugServer.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 51d39ec8268f..8ce84f91daa0 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -449,20 +449,23 @@ public CompletableFuture stepOut(StepOutArguments args) { @Override public CompletableFuture restart(RestartArguments args) { - if (context.getDebugMode() == ExecutionContext.DebugMode.ATTACH) { - outputLogger.sendErrorOutput("Restart is not supported in remote debug mode."); - return CompletableFuture.completedFuture(null); - } + return CompletableFuture.supplyAsync(() -> { + if (context.getDebugMode() == ExecutionContext.DebugMode.ATTACH) { + outputLogger.sendErrorOutput("Restart operation is not supported in remote debug mode."); + return null; + } - try { - resetServer(); - launchDebuggeeProgram(); - return CompletableFuture.completedFuture(null); - } catch (Exception e) { - LOGGER.error("Failed to restart the ballerina program due to: " + e.getMessage(), e); - outputLogger.sendErrorOutput("Failed to restart the ballerina program"); - return CompletableFuture.completedFuture(null); - } + try { + resetServer(); + launchDebuggeeProgram(); + } catch (Exception e) { + LOGGER.error("Failed to restart the Ballerina program due to: {}", e.getMessage(), e); + outputLogger.sendErrorOutput("Failed to restart the Ballerina program"); + terminateDebugSession(context.getDebuggeeVM() != null, true); + } + + return null; + }); } private void launchDebuggeeProgram() throws Exception { From b90dfa035eefabade141eecf1495dd6ac059e3cc Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 6 Jan 2025 11:18:57 +0530 Subject: [PATCH 3/7] Fix minor issues --- .../ballerinalang/langserver/commons/workspace/RunResult.java | 4 +++- .../langserver/command/executors/RunExecutor.java | 2 +- .../langserver/workspace/BallerinaWorkspaceManager.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java index e7107f9889df..3b6543f7944d 100644 --- a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunResult.java @@ -23,8 +23,10 @@ import java.util.Collection; /** - * Represents the result of a run operation. + * Represents the result of {@link WorkspaceManager#run(RunContext)} operation. * + * @param process {@link Process} instance representing the run operation + * @param diagnostics diagnostics generated during the compilation * @since 2201.12.0 */ public record RunResult(Process process, Collection diagnostics) { diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index ad4c957bc135..0680dbfc927e 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -79,7 +79,7 @@ public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorEx LogTraceParams diagnosticMessage = new LogTraceParams(diagnostic.toString(), ERROR_CHANNEL); context.getLanguageClient().logTrace(diagnosticMessage); } - if (diagnostics.stream().anyMatch(diagnostic -> diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR)) { + if (diagnostics.stream().anyMatch(d -> d.diagnosticInfo().severity() == DiagnosticSeverity.ERROR)) { LogTraceParams error = new LogTraceParams("error: compilation contains errors", ERROR_CHANNEL); context.getLanguageClient().logTrace(error); return false; diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index 0640044a4b0a..0ccddb93924b 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -104,6 +104,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -622,7 +623,7 @@ public RunResult run(RunContext executionContext) throws IOException { // Add tool resolution diagnostics to diagnostics diagnostics.addAll(project.currentPackage().getBuildToolResolution().getDiagnosticList()); - if (diagnostics.stream().anyMatch(diagnostic -> diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR)) { + if (diagnostics.stream().anyMatch(d -> d.diagnosticInfo().severity() == DiagnosticSeverity.ERROR)) { return new RunResult(null, diagnostics); } From 47a88261faeda129635fdae74dc1dcfe8c411c6c Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 7 Jan 2025 17:07:16 +0530 Subject: [PATCH 4/7] Update run executor to report BLangCompilerExceptions --- .../langserver/command/executors/RunExecutor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index 0680dbfc927e..279c137b6141 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -22,6 +22,7 @@ import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticSeverity; import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.compiler.BLangCompilerException; import org.ballerinalang.langserver.commons.ExecuteCommandContext; import org.ballerinalang.langserver.commons.client.ExtendedLanguageClient; import org.ballerinalang.langserver.commons.command.CommandArgument; @@ -93,17 +94,19 @@ public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorEx listenOutputAsync(context.getLanguageClient(), process::getInputStream, OUT_CHANNEL); listenOutputAsync(context.getLanguageClient(), process::getErrorStream, ERROR_CHANNEL); return true; + } catch (BLangCompilerException e) { + LogTraceParams error = new LogTraceParams(e.getMessage(), ERROR_CHANNEL); + context.getLanguageClient().logTrace(error); } catch (IOException e) { LogTraceParams error = new LogTraceParams("Error while running the program in fast-run mode: " + e.getMessage(), ERROR_CHANNEL); context.getLanguageClient().logTrace(error); - throw new LSCommandExecutorException(e); } catch (Exception e) { LogTraceParams error = new LogTraceParams("Unexpected error while executing the fast-run: " + e.getMessage(), ERROR_CHANNEL); context.getLanguageClient().logTrace(error); - throw new LSCommandExecutorException(e); } + return false; } private RunContext getWorkspaceRunContext(ExecuteCommandContext context) { From 54c70324b1abd3a26775677508fbf20be9e45a27 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 7 Jan 2025 17:09:05 +0530 Subject: [PATCH 5/7] Add new test cases to cover fast-run diagnostics --- .../workspace/BallerinaWorkspaceManager.java | 2 +- .../workspace/TestWorkspaceManager.java | 73 +++++++++++++------ .../Ballerina.toml | 4 + .../pkg_with_compilation_errors/main.bal | 5 ++ 4 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/Ballerina.toml create mode 100644 language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/main.bal diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index 0ccddb93924b..b99ddfc9060a 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -716,7 +716,7 @@ private void logError(String message) { @Override public boolean stop(Path filePath) { - Optional projectPairOpt = projectContext(projectRoot(filePath)); + Optional projectPairOpt = projectContext(projectRoot(filePath).toAbsolutePath()); if (projectPairOpt.isEmpty()) { clientLogger.logWarning("Failed to stop process: Project not found"); return false; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java index f4b78deb3592..faf28314ab9f 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java @@ -83,6 +83,7 @@ * @since 2.0.0 */ public class TestWorkspaceManager { + private static final Path RESOURCE_DIRECTORY = Path.of("src/test/resources/project"); private final String dummyContent = "function foo() {" + CommonUtil.LINE_SEPARATOR + "}"; private final String dummyDidChangeContent = "function foo1() {" + CommonUtil.LINE_SEPARATOR + "}"; @@ -558,8 +559,23 @@ public void testWSRunStopProject() throws WorkspaceDocumentException, EventSyncException, LSCommandExecutorException { Path projectPath = RESOURCE_DIRECTORY.resolve("long_running"); Path filePath = projectPath.resolve("main.bal"); - ExecuteCommandContext execContext = runViaLs(filePath); - stopViaLs(execContext, projectPath); + RunResult runResult = executeRunCommand(filePath); + Assert.assertTrue(runResult.success()); + Assert.assertEquals(runResult.programOutput[0], "Hello, World!\n"); + executeStopCommand(projectPath); + } + + @Test + public void testWSRunProjectWithCompilationErrors() + throws WorkspaceDocumentException, EventSyncException, LSCommandExecutorException { + Path projectPath = RESOURCE_DIRECTORY.resolve("pkg_with_compilation_errors"); + Path filePath = projectPath.resolve("main.bal"); + RunResult runResult = executeRunCommand(filePath); + Assert.assertFalse(runResult.success()); + Assert.assertTrue(runResult.errorOutput().length > 0); + Assert.assertEquals(runResult.errorOutput()[0], "ERROR [main.bal:(5:1,5:1)] missing semicolon token"); + Assert.assertEquals(runResult.errorOutput()[1], "error: compilation contains errors"); + executeStopCommand(projectPath); } @Test @@ -567,7 +583,8 @@ public void testSemanticApiAfterWSRun() throws WorkspaceDocumentException, EventSyncException, LSCommandExecutorException { Path projectPath = RESOURCE_DIRECTORY.resolve("hello_service"); Path filePath = projectPath.resolve("main.bal"); - ExecuteCommandContext execContext = runViaLs(filePath); + RunResult runResult = executeRunCommand(filePath); + Assert.assertTrue(runResult.success()); // Test syntax tree api JsonElement syntaxTreeJSON = DiagramUtil.getSyntaxTreeJSON(workspaceManager.document(filePath).orElseThrow(), @@ -581,7 +598,7 @@ public void testSemanticApiAfterWSRun() Assert.assertEquals(execPositions.getAsJsonArray().get(0).getAsJsonObject().get("name").getAsString(), "hello"); - stopViaLs(execContext, projectPath); + executeStopCommand(projectPath); } @Test @@ -602,7 +619,8 @@ public void testSemanticApiAfterWSRunMultiMod() workspaceManager.document(filePath).orElseThrow(), semanticModelPreExec); - ExecuteCommandContext execContext = runViaLs(filePath); + RunResult runResult = executeRunCommand(filePath); + Assert.assertTrue(runResult.success()); SemanticModel semanticModelPostExec = workspaceManager.semanticModel(filePath).orElseThrow(); JsonElement syntaxTreeJSONPostExec = DiagramUtil.getSyntaxTreeJSON( @@ -612,33 +630,30 @@ public void testSemanticApiAfterWSRunMultiMod() Gson gson = new GsonBuilder().setPrettyPrinting().create(); Assert.assertEquals(gson.toJson(syntaxTreeJSONPreExec), gson.toJson(syntaxTreeJSONPostExec)); - stopViaLs(execContext, projectPath); + executeStopCommand(projectPath); } - - private ExecuteCommandContext runViaLs(Path filePath) + private RunResult executeRunCommand(Path filePath) throws WorkspaceDocumentException, EventSyncException, LSCommandExecutorException { System.setProperty("java.command", guessJavaPath()); System.setProperty(BALLERINA_HOME, "./build"); workspaceManager.loadProject(filePath); RunExecutor runExecutor = new RunExecutor(); MockSettings mockSettings = Mockito.withSettings().stubOnly(); - ExecuteCommandContext execContext = Mockito.mock(ExecuteCommandContext.class, mockSettings); - CommandArgument arg = CommandArgument.from("path", new JsonPrimitive(filePath.toString())); - Mockito.when(execContext.getArguments()).thenReturn(Collections.singletonList(arg)); - Mockito.when(execContext.workspace()).thenReturn(workspaceManager); + + ExecuteCommandContext execContext = createExecutionContextMock(filePath); ExtendedLanguageClient languageClient = Mockito.mock(ExtendedLanguageClient.class, mockSettings); ArgumentCaptor logCaptor = ArgumentCaptor.forClass(LogTraceParams.class); Mockito.doNothing().when(languageClient).logTrace(logCaptor.capture()); Mockito.when(execContext.getLanguageClient()).thenReturn(languageClient); Boolean didRan = runExecutor.execute(execContext); - Assert.assertTrue(didRan); - Assert.assertEquals(reduceToOutString(logCaptor), "Hello, World!" + System.lineSeparator()); - return execContext; + + return new RunResult(didRan, extractLogs(logCaptor, "out"), extractLogs(logCaptor, "err")); } - private static void stopViaLs(ExecuteCommandContext execContext, Path projectPath) { + private void executeStopCommand(Path projectPath) { StopExecutor stopExecutor = new StopExecutor(); + ExecuteCommandContext execContext = createExecutionContextMock(projectPath); Boolean didStop = stopExecutor.execute(execContext); Assert.assertTrue(didStop); @@ -646,14 +661,22 @@ private static void stopViaLs(ExecuteCommandContext execContext, Path projectPat FileUtils.deleteQuietly(target.toFile()); } - private static String reduceToOutString(ArgumentCaptor logCaptor) { + private ExecuteCommandContext createExecutionContextMock(Path filePath) { + MockSettings mockSettings = Mockito.withSettings().stubOnly(); + ExecuteCommandContext execContext = Mockito.mock(ExecuteCommandContext.class, mockSettings); + + CommandArgument arg = CommandArgument.from("path", new JsonPrimitive(filePath.toString())); + Mockito.when(execContext.getArguments()).thenReturn(Collections.singletonList(arg)); + Mockito.when(execContext.workspace()).thenReturn(workspaceManager); + return execContext; + } + + private static String[] extractLogs(ArgumentCaptor logCaptor, String channel) { List params = waitGetAllValues(logCaptor); - StringBuilder sb = new StringBuilder(); - for (LogTraceParams param : params) { - sb.append(param.getMessage()); - Assert.assertEquals(param.getVerbose(), "out"); // not "err" - } - return sb.toString(); + return params.stream() + .filter(param -> param.getVerbose().equals(channel)) + .map(LogTraceParams::getMessage) + .toArray(String[]::new); } private static List waitGetAllValues(ArgumentCaptor logCaptor) { @@ -742,4 +765,8 @@ public Object[] workspaceEventsTestDataProvider() { RESOURCE_DIRECTORY.resolve("myproject").resolve("main.bal").toAbsolutePath() }; } + + private record RunResult(boolean success, String[] programOutput, String[] errorOutput) { + + } } diff --git a/language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/Ballerina.toml b/language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/Ballerina.toml new file mode 100644 index 000000000000..fb35ec71158a --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "baltest" +name = "pkg_with_compilation_errors" +version = "0.1.0" diff --git a/language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/main.bal b/language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/main.bal new file mode 100644 index 000000000000..b06b45d9ff4f --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/project/pkg_with_compilation_errors/main.bal @@ -0,0 +1,5 @@ +import ballerina/lang.runtime; + +public function main() { + runtime:sleep(20) +} From fa35b794c8de0cd2f8e240b7e6842f1ec8ee7b2b Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 8 Jan 2025 09:28:29 +0530 Subject: [PATCH 6/7] Fix test failures --- .../langserver/workspace/TestWorkspaceManager.java | 2 +- .../debugger/test/utils/client/DAPRequestManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java index faf28314ab9f..a9809417b601 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/workspace/TestWorkspaceManager.java @@ -561,7 +561,7 @@ public void testWSRunStopProject() Path filePath = projectPath.resolve("main.bal"); RunResult runResult = executeRunCommand(filePath); Assert.assertTrue(runResult.success()); - Assert.assertEquals(runResult.programOutput[0], "Hello, World!\n"); + Assert.assertEquals(runResult.programOutput[0].trim(), "Hello, World!"); executeStopCommand(projectPath); } diff --git a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java index 877cfd545c8f..65059e007745 100644 --- a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java +++ b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/client/DAPRequestManager.java @@ -383,7 +383,7 @@ private enum DefaultTimeouts { SET_BREAKPOINTS(10000), CONFIG_DONE(2000), ATTACH(5000), - LAUNCH(10000), + LAUNCH(15000), THREADS(2000), STACK_TRACE(7000), SCOPES(2000), @@ -395,7 +395,7 @@ private enum DefaultTimeouts { STEP_OUT(5000), RESUME(5000), PAUSE(5000), - RESTART(10000), + RESTART(15000), DISCONNECT(5000); private final long value; From cbab642d3ec1bb8f6a4eb850e36b9fdbf1077155 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Thu, 9 Jan 2025 11:23:20 +0530 Subject: [PATCH 7/7] Make restart requests synchronous --- .../debugadapter/JBallerinaDebugServer.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 8ce84f91daa0..b35415ce98d2 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -449,23 +449,21 @@ public CompletableFuture stepOut(StepOutArguments args) { @Override public CompletableFuture restart(RestartArguments args) { - return CompletableFuture.supplyAsync(() -> { - if (context.getDebugMode() == ExecutionContext.DebugMode.ATTACH) { - outputLogger.sendErrorOutput("Restart operation is not supported in remote debug mode."); - return null; - } + if (context.getDebugMode() == ExecutionContext.DebugMode.ATTACH) { + outputLogger.sendErrorOutput("Restart operation is not supported in remote debug mode."); + return CompletableFuture.completedFuture(null); + } - try { - resetServer(); - launchDebuggeeProgram(); - } catch (Exception e) { - LOGGER.error("Failed to restart the Ballerina program due to: {}", e.getMessage(), e); - outputLogger.sendErrorOutput("Failed to restart the Ballerina program"); - terminateDebugSession(context.getDebuggeeVM() != null, true); - } + try { + resetServer(); + launchDebuggeeProgram(); + } catch (Exception e) { + LOGGER.error("Failed to restart the Ballerina program due to: {}", e.getMessage(), e); + outputLogger.sendErrorOutput("Failed to restart the Ballerina program"); + terminateDebugSession(context.getDebuggeeVM() != null, true); + } - return null; - }); + return CompletableFuture.completedFuture(null); } private void launchDebuggeeProgram() throws Exception {