diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java index f8387d892b41..1ceb0d473c30 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/BlendedManifest.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -132,11 +133,12 @@ public static BlendedManifest from(DependencyManifest dependencyManifest, } } } else { + Collection moduleNames = existingDepOptional.isPresent() ? + existingDepOptional.get().modules : Collections.emptyList(); depContainer.add(depInPkgManifest.org(), depInPkgManifest.name(), new Dependency( depInPkgManifest.org(), depInPkgManifest.name(), depInPkgManifest.version(), DependencyRelation.UNKNOWN, REPOSITORY_NOT_SPECIFIED, - moduleNames(new DependencyManifest.Package(depInPkgManifest.name(), depInPkgManifest.org(), - depInPkgManifest.version())), DependencyOrigin.USER_SPECIFIED)); + moduleNames, DependencyOrigin.USER_SPECIFIED)); continue; } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java index fb783a4fbf14..73af87fe78c2 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/ExistingProjectTests.java @@ -60,9 +60,13 @@ public static Object[][] testCaseProvider() { // 14. package contains 2 dependencies one of which is in Ballerina toml file thats not local {"suite-existing_project", "case-0014", true}, {"suite-existing_project", "case-0014", false}, - // 15. package updates transitive dependency from the Ballerian toml file that is not local + // 15. package updates transitive dependency from the Ballerina toml file that is not local {"suite-existing_project", "case-0015", true}, - {"suite-existing_project", "case-0015", false} + {"suite-existing_project", "case-0015", false}, + // 16. package name is hierarchical, there are new versions in the central, + // and the older version is specified in Ballerina.toml and Dependencies.toml + {"suite-existing_project", "case-0016", true}, + {"suite-existing_project", "case-0016", false} }; } } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java index 741c34aeb632..c37ee294934d 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/PackageResolutionTestCaseBuilder.java @@ -156,8 +156,9 @@ private static DependencyManifest getDependencyManifest(Path dependenciesTomlPat } PackageDescriptor pkgDesc = Utils.getPkgDescFromNode(node.name().value(), null); + List modules = Utils.getDependencyModules(pkgDesc, attrs.get("modules")); recordedDeps.add(new DependencyManifest.Package(pkgDesc.name(), pkgDesc.org(), pkgDesc.version(), - scope.getValue(), isTransitive, Collections.emptyList(), Collections.emptyList())); + scope.getValue(), isTransitive, Collections.emptyList(), modules)); } return DependencyManifest.from("2.0.0", null, recordedDeps, Collections.emptyList()); } diff --git a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java index 70fc33cfc4f0..217f758be360 100644 --- a/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java +++ b/compiler/ballerina-lang/src/test/java/io/ballerina/projects/test/resolution/packages/internal/Utils.java @@ -17,6 +17,7 @@ */ package io.ballerina.projects.test.resolution.packages.internal; +import io.ballerina.projects.DependencyManifest; import io.ballerina.projects.DependencyResolutionType; import io.ballerina.projects.ModuleName; import io.ballerina.projects.PackageDependencyScope; @@ -26,6 +27,10 @@ import io.ballerina.projects.PackageVersion; import io.ballerina.projects.environment.ModuleLoadRequest; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Contains utility methods used throughout the test framework. * @@ -49,6 +54,19 @@ public static PackageDependencyScope getDependencyScope(Object scopeValue) { } } + public static List getDependencyModules(PackageDescriptor pkgDesc, Object modulesValue) { + if (modulesValue == null) { + return Collections.emptyList(); + } + List modules = new ArrayList<>(); + String modulesStr = modulesValue.toString(); + String[] moduleNames = modulesStr.split(","); + for (String moduleName : moduleNames) { + modules.add(new DependencyManifest.Module(pkgDesc.org().value(), pkgDesc.name().value(), moduleName)); + } + return modules; + } + public static ModuleLoadRequest getModuleLoadRequest(String name, PackageDependencyScope scope, DependencyResolutionType resolutionType) { diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/Ballerina_toml.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/Ballerina_toml.dot new file mode 100644 index 000000000000..35c34b6f3d93 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/Ballerina_toml.dot @@ -0,0 +1,3 @@ +digraph "BallerinaToml" { + "samjs/qux.foo:1.0.2" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/Dependencies_toml.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/Dependencies_toml.dot new file mode 100644 index 000000000000..726836dee7ad --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/Dependencies_toml.dot @@ -0,0 +1,5 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "samjs/qux.foo:1.0.2" + + "samjs/qux.foo:1.0.2" [modules = "qux.foo"] +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/app.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/app.dot new file mode 100644 index 000000000000..0d86bd4fb272 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/app.dot @@ -0,0 +1,3 @@ +digraph "samejs/app:0.1.0" { + "samjs/qux.foo" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/case-description.md b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/case-description.md new file mode 100644 index 000000000000..ee172831ff94 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/case-description.md @@ -0,0 +1,13 @@ +# Package name is hierarchical, there are new versions in the central, and the older version is specified in Ballerina.toml and Dependencies.toml + +1. User's package has `samjs/qux.foo:1.0.2` as a dependency in Dependencies.toml. +2. A newer version `samjs/qux.foo:1.0.5` has been released to central. +3. User specifies `samjs/qux.foo:1.0.2` in Ballerina.toml +4. User now builds the package + +## Expected behavior + +### Sticky == true +No changes to Dependency graph +### Sticky == false +Dependency graph should be updated to have `samjs/qux.foo:1.0.5` diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/expected-graph-nosticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/expected-graph-nosticky.dot new file mode 100644 index 000000000000..942fdc3f93d5 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/expected-graph-nosticky.dot @@ -0,0 +1,3 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "samjs/qux.foo:1.0.5" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/expected-graph-sticky.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/expected-graph-sticky.dot new file mode 100644 index 000000000000..496cdc7f2cd2 --- /dev/null +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/case-0016/expected-graph-sticky.dot @@ -0,0 +1,3 @@ +digraph "example1" { + "samejs/app:0.1.0" -> "samjs/qux.foo:1.0.2" +} diff --git a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/central.dot b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/central.dot index d33827186532..d3694bd8577f 100644 --- a/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/central.dot +++ b/compiler/ballerina-lang/src/test/resources/package-resolution/suite-existing_project/repositories/central.dot @@ -102,5 +102,11 @@ digraph central { subgraph "ballerina/http:1.4.0" { "ballerina/http:1.4.0" -> "ballerina/io:1.0.2" } + + subgraph "samjs/qux.foo:1.0.2" { + } + + subgraph "samjs/qux.foo:1.0.5" { + } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java index 9d0b8b935ad2..8433049daa2f 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java @@ -22,17 +22,15 @@ import com.sun.jdi.ThreadReference; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.request.BreakpointRequest; -import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.StepRequest; import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint; import org.ballerinalang.debugadapter.breakpoint.LogMessage; import org.ballerinalang.debugadapter.breakpoint.TemplateLogMessage; -import org.ballerinalang.debugadapter.config.ClientConfigHolder; -import org.ballerinalang.debugadapter.config.ClientLaunchConfigHolder; import org.ballerinalang.debugadapter.evaluation.BExpressionValue; import org.ballerinalang.debugadapter.evaluation.DebugExpressionEvaluator; import org.ballerinalang.debugadapter.evaluation.EvaluationException; import org.ballerinalang.debugadapter.evaluation.EvaluationExceptionKind; +import org.ballerinalang.debugadapter.jdi.JDIUtils; import org.ballerinalang.debugadapter.jdi.JdiProxyException; import org.ballerinalang.debugadapter.jdi.StackFrameProxyImpl; import org.ballerinalang.debugadapter.jdi.ThreadReferenceProxyImpl; @@ -48,6 +46,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -107,9 +106,10 @@ void processBreakpointEvent(BreakpointEvent bpEvent) { // need to internally step out if we are at the last line of a function, in order to ignore having debug hits // on the last line. if (requireStepOut(bpEvent)) { - activateDynamicBreakPoints((int) bpEvent.thread().uniqueID(), DynamicBreakpointMode.CALLER); + activateDynamicBreakPoints((int) bpEvent.thread().uniqueID(), DynamicBreakpointMode.CALLER, true); + context.setPrevInstruction(DebugInstruction.STEP_OVER); context.getDebuggeeVM().resume(); - } else if (context.getLastInstruction() != null && context.getLastInstruction() != DebugInstruction.CONTINUE) { + } else if (context.getPrevInstruction() != null && context.getPrevInstruction() != DebugInstruction.CONTINUE) { jdiEventProcessor.notifyStopEvent(bpEvent); } else if (fileBreakpoints == null || !fileBreakpoints.containsKey(lineNumber)) { jdiEventProcessor.notifyStopEvent(bpEvent); @@ -168,19 +168,13 @@ private void processAdvanceBreakpoints(BreakpointEvent event, BalBreakpoint brea /** * Responsible for clearing dynamic(temporary) breakpoints used for step over instruction and for restoring the * original user breakpoints before proceeding with the other debug instructions. - * - * @param instruction debug instruction */ - void restoreUserBreakpoints(DebugInstruction instruction) { + void restoreUserBreakpoints() { if (context.getDebuggeeVM() == null) { return; } - context.getEventManager().deleteAllBreakpoints(); - if (instruction == DebugInstruction.CONTINUE || instruction == DebugInstruction.STEP_OVER) { - context.getDebuggeeVM().allClasses().forEach(referenceType -> - activateUserBreakPoints(referenceType, false)); - } + context.getDebuggeeVM().allClasses().forEach(classRef -> activateUserBreakPoints(classRef, false)); } /** @@ -191,13 +185,6 @@ void restoreUserBreakpoints(DebugInstruction instruction) { */ void activateUserBreakPoints(ReferenceType referenceType, boolean shouldNotify) { try { - // avoids setting break points if the server is running in 'no-debug' mode. - ClientConfigHolder configHolder = context.getAdapter().getClientConfigHolder(); - if (configHolder instanceof ClientLaunchConfigHolder - && ((ClientLaunchConfigHolder) configHolder).isNoDebugMode()) { - return; - } - String qualifiedClassName = getQualifiedClassName(referenceType); if (!userBreakpoints.containsKey(qualifiedClassName)) { return; @@ -206,6 +193,7 @@ void activateUserBreakPoints(ReferenceType referenceType, boolean shouldNotify) for (BalBreakpoint breakpoint : breakpoints.values()) { List locations = referenceType.locationsOfLine(breakpoint.getLine()); if (!locations.isEmpty()) { + // TODO: should we consider the last location instead? Location loc = locations.get(0); BreakpointRequest bpReq = context.getEventManager().createBreakpointRequest(loc); bpReq.enable(); @@ -230,23 +218,32 @@ void activateUserBreakPoints(ReferenceType referenceType, boolean shouldNotify) * Activates dynamic/temporary breakpoints (which will be used to process the STEP-OVER instruction) via Java Debug * Interface(JDI). * - * @param threadId ID of the active java thread in oder to configure dynamic breakpoint on the active stack trace - * @param mode dynamic breakpoint mode + * @param threadId ID of the active java thread in oder to configure dynamic breakpoint on the active stack + * trace. + * @param mode dynamic breakpoint mode. + * @param validate If true, validates whether the dynamic breakpoints are already applied before activating dynamic + * breakpoints. This is to optimize the performance by avoiding redundant breakpoint activations. */ - void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode) { - ThreadReferenceProxyImpl threadReference = context.getAdapter().getAllThreads().get(threadId); + void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolean validate) { try { + ThreadReferenceProxyImpl threadReference = context.getAdapter().getAllThreads().get(threadId); List jStackFrames = threadReference.frames(); List validFrames = jdiEventProcessor.filterValidBallerinaFrames(jStackFrames); if (mode == DynamicBreakpointMode.CURRENT && !validFrames.isEmpty()) { - configureBreakpointsForMethod(validFrames.get(0)); + Location currentLocation = validFrames.get(0).getJStackFrame().location(); + Optional prevLocation = context.getPrevLocation(); + if (!validate || prevLocation.isEmpty() || !isWithinSameSource(currentLocation, prevLocation.get())) { + doActivateDynamicBreakPoints(currentLocation); + } + context.setPrevLocation(currentLocation); } + // If the current function is invoked within another ballerina function, we need to explicitly set another // temporary breakpoint on the location of its invocation. This is supposed to handle the situations where // the user wants to step over on an exit point of the current function. if (mode == DynamicBreakpointMode.CALLER && validFrames.size() > 1) { - configureBreakpointsForMethod(validFrames.get(1)); + doActivateDynamicBreakPoints(validFrames.get(1).getJStackFrame().location()); } } catch (JdiProxyException e) { LOGGER.error(e.getMessage()); @@ -257,16 +254,35 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode) { } } + private void doActivateDynamicBreakPoints(Location location) { + context.getEventManager().deleteAllBreakpoints(); + configureBreakpointsForMethod(location); + context.setPrevLocation(location); + } + + /** + * Checks whether the given two locations are within the same source file. + * + * @param currentLocation current location + * @param prevLocation previous location + * @return true if the given two locations are within the same source file, false otherwise + */ + private boolean isWithinSameSource(Location currentLocation, Location prevLocation) { + try { + return Objects.equals(currentLocation.sourcePath(), prevLocation.sourcePath()); + } catch (AbsentInformationException e) { + return false; + } + } + /** * Configures temporary(dynamic) breakpoints for all the lines within the method, which encloses the given stack * frame location. This strategy is used when processing STEP_OVER requests. * - * @param balStackFrame stack frame which contains the method information + * @param currentLocation current stack frame location */ - private void configureBreakpointsForMethod(BallerinaStackFrame balStackFrame) { + private void configureBreakpointsForMethod(Location currentLocation) { try { - Location currentLocation = balStackFrame.getJStackFrame().location(); - ReferenceType referenceType = currentLocation.declaringType(); List allLocations = currentLocation.method().allLineLocations(); Optional firstLocation = allLocations.stream() .filter(location -> location.lineNumber() > 0) @@ -278,7 +294,7 @@ private void configureBreakpointsForMethod(BallerinaStackFrame balStackFrame) { int nextStepPoint = firstLocation.get().lineNumber(); do { - List locations = referenceType.locationsOfLine(nextStepPoint); + List locations = currentLocation.method().locationsOfLine(nextStepPoint); if (!locations.isEmpty() && (locations.get(0).lineNumber() > firstLocation.get().lineNumber())) { // Checks whether there are any user breakpoint configured for the same location, before adding the // dynamic breakpoint. @@ -291,7 +307,7 @@ private void configureBreakpointsForMethod(BallerinaStackFrame balStackFrame) { } nextStepPoint++; } while (nextStepPoint <= lastLocation.get().lineNumber()); - } catch (AbsentInformationException | JdiProxyException e) { + } catch (AbsentInformationException e) { LOGGER.error(e.getMessage()); } } @@ -362,8 +378,7 @@ private BExpressionValue evaluateExpressionSafely(String expression, ThreadRefer // the new event. If this new EventSet is in 'SUSPEND_ALL' mode, then a deadlock will occur because no one // will resume the EventSet. Therefore to avoid this, we are disabling possible event requests before doing // the condition evaluation. - context.getEventManager().classPrepareRequests().forEach(EventRequest::disable); - context.getEventManager().breakpointRequests().forEach(BreakpointRequest::disable); + JDIUtils.disableJDIRequests(context); ThreadReferenceProxyImpl thread = context.getAdapter().getAllThreads().get((int) threadReference.uniqueID()); List validFrames = jdiEventProcessor.filterValidBallerinaFrames(thread.frames()); @@ -377,16 +392,16 @@ private BExpressionValue evaluateExpressionSafely(String expression, ThreadRefer evaluator.setExpression(expression); BExpressionValue evaluationResult = evaluator.evaluate(); - // As we are disabling all the breakpoint requests before evaluating the user's conditional + // As we disabled all the breakpoint requests before evaluating the user's conditional // expression, need to re-enable all the breakpoints before continuing the remote VM execution. - restoreUserBreakpoints(context.getLastInstruction()); + JDIUtils.enableJDIRequests(context); return evaluationResult; } private boolean requireStepOut(BreakpointEvent event) { try { - if (context.getLastInstruction() != DebugInstruction.STEP_OVER - && context.getLastInstruction() != DebugInstruction.STEP_OUT) { + if (context.getPrevInstruction() != DebugInstruction.STEP_OVER + && context.getPrevInstruction() != DebugInstruction.STEP_OUT) { return false; } Location currentLocation = event.location(); @@ -415,7 +430,7 @@ private void notifyBreakPointChangesToClient(BalBreakpoint balBreakpoint) { /** * Dynamic Breakpoint Options. */ - enum DynamicBreakpointMode { + public enum DynamicBreakpointMode { /** * Configures dynamic breakpoints only for the current method (active stack frame). */ diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java index 9fcdba5595ae..04d26d2870f7 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/ExecutionContext.java @@ -16,6 +16,7 @@ package org.ballerinalang.debugadapter; +import com.sun.jdi.Location; import com.sun.jdi.request.EventRequestManager; import io.ballerina.projects.Project; import org.ballerinalang.debugadapter.jdi.VirtualMachineProxyImpl; @@ -39,14 +40,16 @@ public class ExecutionContext { private String sourceProjectRoot; private final DebugProjectCache projectCache; private Process launchedProcess; - private DebugInstruction lastInstruction; private boolean terminateRequestReceived; private boolean supportsRunInTerminalRequest; + private DebugInstruction prevInstruction; + private Location prevLocation; ExecutionContext(JBallerinaDebugServer adapter) { this.adapter = adapter; this.projectCache = new DebugProjectCache(); - this.lastInstruction = DebugInstruction.CONTINUE; + this.prevInstruction = DebugInstruction.CONTINUE; + this.prevLocation = null; } public Optional getLaunchedProcess() { @@ -102,12 +105,20 @@ public BufferedReader getErrorStream() { return new BufferedReader(new InputStreamReader(launchedProcess.getErrorStream(), StandardCharsets.UTF_8)); } - public DebugInstruction getLastInstruction() { - return lastInstruction; + public DebugInstruction getPrevInstruction() { + return prevInstruction; } - public void setLastInstruction(DebugInstruction lastInstruction) { - this.lastInstruction = lastInstruction; + public void setPrevInstruction(DebugInstruction prevInstruction) { + this.prevInstruction = prevInstruction; + } + + public Optional getPrevLocation() { + return Optional.ofNullable(prevLocation); + } + + public void setPrevLocation(Location prevLocation) { + this.prevLocation = prevLocation; } public DebugMode getDebugMode() { 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 b6d854e42bf6..0d84b61b4a4c 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 @@ -30,6 +30,7 @@ import io.ballerina.identifier.Utils; import io.ballerina.projects.Project; import io.ballerina.projects.directory.SingleFileProject; +import org.ballerinalang.debugadapter.BreakpointProcessor.DynamicBreakpointMode; import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint; import org.ballerinalang.debugadapter.completion.CompletionGenerator; import org.ballerinalang.debugadapter.completion.context.CompletionContext; @@ -215,6 +216,11 @@ public CompletableFuture initialize(InitializeRequestArguments arg @Override public CompletableFuture setBreakpoints(SetBreakpointsArguments args) { return CompletableFuture.supplyAsync(() -> { + SetBreakpointsResponse bpResponse = new SetBreakpointsResponse(); + if (isNoDebugMode()) { + return bpResponse; + } + BalBreakpoint[] balBreakpoints = Arrays.stream(args.getBreakpoints()) .map((SourceBreakpoint sourceBreakpoint) -> toBreakpoint(sourceBreakpoint, args.getSource())) .toArray(BalBreakpoint[]::new); @@ -224,7 +230,6 @@ public CompletableFuture setBreakpoints(SetBreakpointsAr breakpointsMap.put(bp.getLine(), bp); } - SetBreakpointsResponse bpResponse = new SetBreakpointsResponse(); String sourcePathUri = args.getSource().getPath(); Optional qualifiedClassName = getQualifiedClassName(context, sourcePathUri); if (qualifiedClassName.isEmpty()) { @@ -425,7 +430,7 @@ public CompletableFuture source(SourceArguments args) { @Override public CompletableFuture continue_(ContinueArguments args) { - prepareFor(DebugInstruction.CONTINUE); + prepareFor(DebugInstruction.CONTINUE, args.getThreadId()); context.getDebuggeeVM().resume(); ContinueResponse continueResponse = new ContinueResponse(); continueResponse.setAllThreadsContinued(true); @@ -434,29 +439,25 @@ public CompletableFuture continue_(ContinueArguments args) { @Override public CompletableFuture next(NextArguments args) { - prepareFor(DebugInstruction.STEP_OVER); + prepareFor(DebugInstruction.STEP_OVER, args.getThreadId()); eventProcessor.sendStepRequest(args.getThreadId(), StepRequest.STEP_OVER); return CompletableFuture.completedFuture(null); } @Override public CompletableFuture stepIn(StepInArguments args) { - prepareFor(DebugInstruction.STEP_IN); + prepareFor(DebugInstruction.STEP_IN, args.getThreadId()); eventProcessor.sendStepRequest(args.getThreadId(), StepRequest.STEP_INTO); return CompletableFuture.completedFuture(null); } @Override public CompletableFuture stepOut(StepOutArguments args) { - stepOut(args.getThreadId()); + prepareFor(DebugInstruction.STEP_OUT, args.getThreadId()); + eventProcessor.sendStepRequest(args.getThreadId(), StepRequest.STEP_OUT); return CompletableFuture.completedFuture(null); } - void stepOut(int threadId) { - prepareFor(DebugInstruction.STEP_OUT); - eventProcessor.sendStepRequest(threadId, StepRequest.STEP_OUT); - } - @Override public CompletableFuture setExceptionBreakpoints(SetExceptionBreakpointsArguments args) { return CompletableFuture.completedFuture(null); @@ -1119,10 +1120,23 @@ private void attachToRemoteVM(String hostName, int portName) throws IOException, /** * Clears previous state information and prepares for the given debug instruction type execution. */ - private void prepareFor(DebugInstruction instruction) { + private void prepareFor(DebugInstruction instruction, int threadId) { clearState(); - eventProcessor.getBreakpointProcessor().restoreUserBreakpoints(instruction); - context.setLastInstruction(instruction); + BreakpointProcessor bpProcessor = eventProcessor.getBreakpointProcessor(); + DebugInstruction prevInstruction = context.getPrevInstruction(); + if (prevInstruction == DebugInstruction.STEP_OVER && instruction == DebugInstruction.CONTINUE) { + bpProcessor.restoreUserBreakpoints(); + } else if (prevInstruction == DebugInstruction.CONTINUE && instruction == DebugInstruction.STEP_OVER) { + bpProcessor.activateDynamicBreakPoints(threadId, DynamicBreakpointMode.CURRENT, false); + } else if (instruction == DebugInstruction.STEP_OVER) { + bpProcessor.activateDynamicBreakPoints(threadId, DynamicBreakpointMode.CURRENT, true); + } + context.setPrevInstruction(instruction); + } + + private boolean isNoDebugMode() { + ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder(); + return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode(); } /** diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java index 37b8a123888a..b538daf2e89b 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java @@ -44,7 +44,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; -import static org.ballerinalang.debugadapter.BreakpointProcessor.DynamicBreakpointMode; import static org.ballerinalang.debugadapter.JBallerinaDebugServer.isBalStackFrame; import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT; @@ -101,7 +100,7 @@ void startListening() { private void processEvent(EventSet eventSet, Event event) { if (event instanceof ClassPrepareEvent evt) { - if (context.getLastInstruction() != DebugInstruction.STEP_OVER) { + if (context.getPrevInstruction() != DebugInstruction.STEP_OVER) { breakpointProcessor.activateUserBreakPoints(evt.referenceType(), true); } eventSet.resume(); @@ -136,9 +135,7 @@ void enableBreakpoints(String qualifiedClassName, LinkedHashMap + * Therefore, to avoid this, we are disabling possible event requests (only class prepare and breakpoint requests + * for now) before invoking the method. + */ + public static void disableJDIRequests(ExecutionContext context) { + EventRequestManager eventManager = context.getEventManager(); + eventManager.classPrepareRequests().forEach(EventRequest::disable); + eventManager.breakpointRequests().forEach(EventRequest::disable); + } + + /** + * Used to re-enable the event requests after the method invocation is completed. + */ + public static void enableJDIRequests(ExecutionContext context) { + EventRequestManager eventManager = context.getEventManager(); + eventManager.breakpointRequests().forEach(EventRequest::enable); + } +} diff --git a/project-api/project-api-test/src/test/java/io/ballerina/projects/test/PackageResolutionTests.java b/project-api/project-api-test/src/test/java/io/ballerina/projects/test/PackageResolutionTests.java index b66c925a162b..a8da4116a1f5 100644 --- a/project-api/project-api-test/src/test/java/io/ballerina/projects/test/PackageResolutionTests.java +++ b/project-api/project-api-test/src/test/java/io/ballerina/projects/test/PackageResolutionTests.java @@ -96,7 +96,6 @@ * * @since 2.0.0 */ -@Test(groups = "broken") public class PackageResolutionTests extends BaseTest { private static final Path RESOURCE_DIRECTORY = Path.of( "src/test/resources/projects_for_resolution_tests").toAbsolutePath(); @@ -115,6 +114,7 @@ public void setup() throws IOException { replaceDependenciesTomlVersion(tempResourceDir.resolve("package_n")); replaceDependenciesTomlVersion(tempResourceDir.resolve("package_p_withDep")); replaceDependenciesTomlVersion(tempResourceDir.resolve("package_p_withoutDep")); + replaceDependenciesTomlVersion(tempResourceDir.resolve("package_z")); } @Test(description = "tests resolution with zero direct dependencies") @@ -561,8 +561,8 @@ public void testProjectWithManyDependencies(boolean optimizeDependencyCompilatio if (os instanceof UnixOperatingSystemMXBean unixOperatingSystemMXBean) { initialOpenCount = unixOperatingSystemMXBean.getOpenFileDescriptorCount(); } - Project project = TestUtils.loadProject( - Path.of("projects_for_resolution_tests/ultimate_package_resolution/package_http"), + Project project = TestUtils.loadProject(tempResourceDir + .resolve("ultimate_package_resolution/package_http"), BuildOptions.builder().setOptimizeDependencyCompilation(optimizeDependencyCompilation).build()); PackageCompilation compilation = project.currentPackage().getCompilation(); @@ -617,9 +617,11 @@ public void testBalaProjectDependencyResolution() { } @Test (description = "Resolve a dependency from the local repo", dataProvider = "optimizeDependencyCompilation") - public void testResolveDependencyFromCustomRepo(boolean optimizeDependencyCompilation) { + public void testResolveDependencyFromCustomRepo(boolean optimizeDependencyCompilation) throws IOException { Path projectDirPath = tempResourceDir.resolve("package_b"); + replaceDistributionVersionOfDependenciesToml(projectDirPath, RepoUtils.getBallerinaShortVersion()); String dependencyContent = """ + [[dependency]] org = "samjs" name = "package_c" @@ -634,14 +636,17 @@ public void testResolveDependencyFromCustomRepo(boolean optimizeDependencyCompil BuildProject project = TestUtils.loadBuildProject(projectEnvironmentBuilder, projectDirPath, buildOptions); // 2) set local repository to dependency - project.currentPackage().dependenciesToml().orElseThrow().modify().withContent(dependencyContent).apply(); + String currentContent = project.currentPackage().ballerinaToml().get() + .tomlDocument().textDocument().toString(); + String updatedContent = currentContent.concat(dependencyContent); + project.currentPackage().ballerinaToml().orElseThrow().modify().withContent(updatedContent).apply(); // 3) Compile and check the diagnostics PackageCompilation compilation = project.currentPackage().getCompilation(); DiagnosticResult diagnosticResult = compilation.diagnosticResult(); // 4) The dependency is expected to load from distribution cache, hence zero diagnostics - Assert.assertEquals(diagnosticResult.errorCount(), 2); + Assert.assertEquals(diagnosticResult.errorCount(), 0); } // For this to be enabled, #31026 should be fixed. @@ -772,8 +777,8 @@ public void testPackageResolutionOfDependencyInvalidRepository() { // Check whether there are any diagnostics DiagnosticResult diagnosticResult = compilation.diagnosticResult(); diagnosticResult.diagnostics().forEach(OUT::println); - Assert.assertEquals(diagnosticResult.errorCount(), 4, "Unexpected compilation diagnostics"); - Assert.assertEquals(diagnosticResult.warningCount(), 1, "Unexpected compilation diagnostics"); + Assert.assertEquals(diagnosticResult.errorCount(), 3, "Unexpected compilation diagnostics"); + Assert.assertEquals(diagnosticResult.warningCount(), 2, "Unexpected compilation diagnostics"); Iterator diagnosticIterator = diagnosticResult.diagnostics().iterator(); Assert.assertTrue(diagnosticIterator.next().toString().contains( @@ -782,8 +787,8 @@ public void testPackageResolutionOfDependencyInvalidRepository() { // Check dependency cannot be resolved diagnostic Assert.assertEquals( diagnosticIterator.next().toString(), - "ERROR [Ballerina.toml:(21:12,21:21)] invalid 'repository' under [dependency]: 'repository' " + - "can only have the value 'local'"); + "WARNING [Ballerina.toml:(17:1,21:21)] Provided custom repository (invalid) cannot be found in the " + + "Settings.toml. "); Assert.assertEquals(diagnosticIterator.next().toString(), "ERROR [fee.bal:(1:1,1:16)] cannot resolve module 'ccc/ddd'"); Assert.assertEquals(diagnosticIterator.next().toString(), @@ -878,6 +883,29 @@ public void testDependencyResolutionWithTransitiveDependencyBuiltFromHigherDistV Assert.assertEquals(diagnosticResult.diagnosticCount(), 0, "Unexpected compilation diagnostics"); } + @Test(description = "Resolve dependencies when a dependency has a hierarchical name and is specified in " + + "the Ballerina.toml and the Dependencies.toml of the dependent") + public void testDependencyWithHierrarchicalNameInDepTomlAndBalToml() { + Path projectDirPath = tempResourceDir.resolve("package_z"); + + BCompileUtil.compileAndCacheBala("projects_for_resolution_tests/package_zz_1_0_0"); + BCompileUtil.compileAndCacheBala("projects_for_resolution_tests/package_zz_1_0_2"); + + BuildOptions.BuildOptionsBuilder buildOptionsBuilder = BuildOptions.builder(); + buildOptionsBuilder.setSticky(true); + BuildOptions buildOptions = buildOptionsBuilder.build(); + Project loadProject = TestUtils.loadBuildProject(projectDirPath, buildOptions); + + PackageCompilation compilation = loadProject.currentPackage().getCompilation(); + + DependencyGraph dependencyGraph = compilation.getResolution().dependencyGraph(); + ResolvedPackageDependency packageO = + dependencyGraph.getNodes().stream().filter( + node -> node.packageInstance().manifest().name().toString().equals("foo.bar") + ).findFirst().orElseThrow(); + Assert.assertEquals(packageO.packageInstance().manifest().version().toString(), "1.0.0"); + } + private void replaceDependenciesTomlVersion(Path projectPath) throws IOException { String currentDistrVersion = RepoUtils.getBallerinaShortVersion(); Path dependenciesTomlTemplatePath = projectPath.resolve("Dependencies-template.toml"); diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/Ballerina.toml b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/Ballerina.toml new file mode 100644 index 000000000000..876a59aced0d --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/Ballerina.toml @@ -0,0 +1,9 @@ +[package] +org = "samjs" +name = "package_z" +version = "0.1.0" + +[[dependency]] +org = "samjs" +name = "foo.bar" +version = "1.0.0" diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/Dependencies-template.toml b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/Dependencies-template.toml new file mode 100644 index 000000000000..97a3d7933075 --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/Dependencies-template.toml @@ -0,0 +1,28 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "**INSERT_DISTRIBUTION_VERSION_HERE**" + +[[package]] +org = "samjs" +name = "foo.bar" +version = "1.0.0" +modules = [ + {org = "samjs", packageName = "foo.bar", moduleName = "foo.bar"} +] + +[[package]] +org = "samjs" +name = "package_z" +version = "0.1.0" +dependencies = [ + {org = "samjs", name = "foo.bar"} +] +modules = [ + {org = "samjs", packageName = "package_z", moduleName = "package_z"} +] + diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/main.bal b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/main.bal new file mode 100644 index 000000000000..69ab50b5833c --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_z/main.bal @@ -0,0 +1,4 @@ +import samjs/foo.bar as _; + +public function func_z() { +} diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_0/Ballerina.toml b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_0/Ballerina.toml new file mode 100644 index 000000000000..638983bf40e5 --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_0/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "samjs" +name = "foo.bar" +version = "1.0.0" diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_0/main.bal b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_0/main.bal new file mode 100644 index 000000000000..e945fa6aef10 --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_0/main.bal @@ -0,0 +1,2 @@ +public function func() { +} diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_2/Ballerina.toml b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_2/Ballerina.toml new file mode 100644 index 000000000000..299ae2a724f4 --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_2/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "samjs" +name = "foo.bar" +version = "1.0.2" diff --git a/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_2/main.bal b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_2/main.bal new file mode 100644 index 000000000000..e945fa6aef10 --- /dev/null +++ b/project-api/project-api-test/src/test/resources/projects_for_resolution_tests/package_zz_1_0_2/main.bal @@ -0,0 +1,2 @@ +public function func() { +} diff --git a/project-api/project-api-test/src/test/resources/testng.xml b/project-api/project-api-test/src/test/resources/testng.xml index 0bf8869cb657..291ebf384a0f 100644 --- a/project-api/project-api-test/src/test/resources/testng.xml +++ b/project-api/project-api-test/src/test/resources/testng.xml @@ -20,12 +20,7 @@ under the License. - - - - - - + diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java index 041df59f7acb..af1b60f54820 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugInstructionTest.java @@ -73,12 +73,8 @@ public void breakpointInBetweenStepOverTest() throws BallerinaTestException { debugTestRunner.initDebugSession(DebugUtils.DebuggeeExecutionKind.RUN); Pair debugHitInfo = debugTestRunner.waitForDebugHit(25000); - // Debugger should hit the breakpoint inside the function invocation when trying to step over. - debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.STEP_OVER); - debugHitInfo = debugTestRunner.waitForDebugHit(10000); - Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 46)); - - // Should go back to the caller function when stepping over again. + // Debugger should not hit the breakpoint inside function invocations when trying to step over the caller + // function. debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.STEP_OVER); debugHitInfo = debugTestRunner.waitForDebugHit(10000); Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 31)); diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/RecursiveDebugTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/RecursiveDebugTest.java index c9cb706cc6d2..2e2363d8eec6 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/RecursiveDebugTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/RecursiveDebugTest.java @@ -63,12 +63,12 @@ public void testStepRequestsOnRecursiveFunctions() throws BallerinaTestException debugHitInfo = debugTestRunner.waitForDebugHit(10000); Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 26)); - // tries STEP_OVER request on the recursive function call line, which should results in a debug hit on the - // user configured breakpoint (since STEP_OVER request should honor any breakpoints which get reached during - // the program execution, before reaching to the next line of the same method). + // tries STEP_OVER request on the recursive function call line, which should not results in a debug hit on the + // user configured breakpoint (since STEP requests should not depend on any breakpoints which get reached + // during the program execution, before reaching to the next line of the same method). debugTestRunner.resumeProgram(debugHitInfo.getRight(), DebugTestRunner.DebugResumeKind.STEP_OVER); debugHitInfo = debugTestRunner.waitForDebugHit(10000); - Assert.assertEquals(debugHitInfo.getLeft(), debugTestRunner.testBreakpoints.get(0)); + Assert.assertEquals(debugHitInfo.getLeft(), new BallerinaTestDebugPoint(debugTestRunner.testEntryFilePath, 26)); // Todo - enable after fixing https://github.com/ballerina-platform/ballerina-lang/issues/34847 // since now the breakpoint is removed, STEP_OVER request should result in a debug hit on next immediate