Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add coloring to the stack frames of the debug thread view #81

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion org.eclipse.jdt.debug.tests/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.jdt.debug.tests; singleton:=true
Bundle-Version: 3.12.600.qualifier
Bundle-Version: 3.12.700.qualifier
Bundle-ClassPath: javadebugtests.jar
Bundle-Activator: org.eclipse.jdt.debug.testplugin.JavaTestPlugin
Bundle-Vendor: %providerName
Expand Down
2 changes: 1 addition & 1 deletion org.eclipse.jdt.debug.tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</parent>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.debug.tests</artifactId>
<version>3.12.600-SNAPSHOT</version>
<version>3.12.700-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
<properties>
<testSuite>${project.artifactId}</testSuite>
Expand Down
37 changes: 37 additions & 0 deletions org.eclipse.jdt.debug.tests/testprograms/StackFrameColoring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2025 Zsombor Gegesy and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Zsombor Gegesy - initial API and implementation
*******************************************************************************/

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StackFrameColoring {

public static void main(String[] args) {
new StackFrameColoring().run();
}

void run() {
List<String> result = Arrays.asList("hello", "world").stream().map(value -> {
breakpointMethod();
return value;
}).collect(Collectors.toList());
System.out.println("StackFrameColoring.run called: "+ result);
}

public void breakpointMethod() {
System.out.println("set a breakpoint here");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation
public static final String CLONE_SUFFIX = "Clone";

final String[] LAUNCH_CONFIG_NAMES_1_4 = { "LargeSourceFile", "LotsOfFields",
"Breakpoints",
"Breakpoints", "StackFrameColoring",
"InstanceVariablesTests",
"LocalVariablesTests", "LocalVariableTests2", "StaticVariablesTests",
"DropTests", "ThrowsNPE", "ThrowsException", "org.eclipse.debug.tests.targets.Watchpoint",
Expand Down Expand Up @@ -2927,7 +2927,7 @@ protected void assertNoErrorMarkersExist(IProject[] projects) throws Exception {
protected void assertNoErrorMarkersExist(IProject project) throws Exception {
if (project.isAccessible()) {
IMarker[] projectMarkers = project.findMarkers(null, false, IResource.DEPTH_INFINITE);
List<IMarker> errorMarkers = Arrays.stream(projectMarkers).filter(marker -> isErrorMarker(marker)).collect(Collectors.toList());
List<IMarker> errorMarkers = Arrays.stream(projectMarkers).filter(AbstractDebugTest::isErrorMarker).toList();
String projectErrors = toString(errorMarkers);
assertEquals("found errors on project " + project + ":" + System.lineSeparator() + projectErrors, Collections.EMPTY_LIST, errorMarkers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@
import org.eclipse.jdt.debug.tests.ui.DebugHoverTests;
import org.eclipse.jdt.debug.tests.ui.DebugViewTests;
import org.eclipse.jdt.debug.tests.ui.DetailPaneManagerTests;
import org.eclipse.jdt.debug.tests.ui.GroupedStackFrameTest;
import org.eclipse.jdt.debug.tests.ui.JavaSnippetEditorTest;
import org.eclipse.jdt.debug.tests.ui.OpenFromClipboardTests;
import org.eclipse.jdt.debug.tests.ui.StackFramePresentationProviderTest;
import org.eclipse.jdt.debug.tests.ui.ViewManagementTests;
import org.eclipse.jdt.debug.tests.ui.VirtualThreadsDebugViewTests;
import org.eclipse.jdt.debug.tests.ui.presentation.ModelPresentationTests;
Expand Down Expand Up @@ -243,6 +245,8 @@ public AutomatedSuite() {
addTest(new TestSuite(StepFilterTests.class));
addTest(new TestSuite(StepIntoSelectionTests.class));
addTest(new TestSuite(InstanceFilterTests.class));
addTest(new TestSuite(StackFramePresentationProviderTest.class));
addTest(new TestSuite(GroupedStackFrameTest.class));
if (JavaProjectHelper.isJava6Compatible()) {
addTest(new TestSuite(ForceReturnTests.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,22 +147,22 @@ protected IJavaThread launchToBreakpoint(String typeName, String breakpointMetho
return thread;
}

protected void assertStackFrameIsSelected(String breakpointMethodName) throws Exception {
protected TreeItem assertStackFrameIsSelected(String breakpointMethodName) throws Exception {
// Get and check the selection form the tree, we expect only one method selected
TreeItem[] selected = getSelectedItemsFromDebugView(true);
Object[] selectedText = selectedText(selected);
if (selected.length != 1) {
if (Platform.OS.isMac()) {
// skip this test on Mac - see bug 516024
return;
return null;
}
throw new TestAgainException("Unexpected selection: " + Arrays.toString(selectedText));
}
assertEquals("Unexpected selection: " + Arrays.toString(selectedText), 1, selected.length);
IJavaStackFrame selectedFrame = selectedFrame(selected);

assertEquals("\"breakpointMethod\" should be selected after reaching breakpoint", selectedFrame.getMethodName(), breakpointMethodName);

return selected[0];
}

@Override
Expand Down Expand Up @@ -208,7 +208,7 @@ protected void waitForNonConsoleJobs() throws Exception {
}

protected Object[] selectedText(TreeItem[] selected) throws Exception {
Object[] selectedText = sync(() -> Arrays.stream(selected).map(x -> x.getText()).toArray());
Object[] selectedText = sync(() -> Arrays.stream(selected).map(TreeItem::getText).toArray());
return selectedText;
}

Expand All @@ -235,7 +235,7 @@ protected String dumpFrames(Object[] childrenData) {

protected TreeItem[] getSelectedItemsFromDebugView(boolean wait) throws Exception {
return sync(() -> {
Tree tree = (Tree) debugView.getViewer().getControl();
Tree tree = getDebugViewTree();
TreeItem[] selected = tree.getSelection();
if (!wait) {
return selected;
Expand All @@ -254,6 +254,24 @@ protected TreeItem[] getSelectedItemsFromDebugView(boolean wait) throws Exceptio
});
}

private Tree getDebugViewTree() {
return (Tree) debugView.getViewer().getControl();
}

protected IJavaThread runCodeUntilBreakpoint(String typeName, String breakpointMethodName) throws Exception {
sync(() -> getActivePage().hideView(getActivePage().findView(IDebugUIConstants.ID_DEBUG_VIEW)));

waitForNonConsoleJobs();
assertNoErrorMarkersExist();
setPreferenceToShowSystemThreads();
sync(() -> openEditor(typeName + ".java"));

var thread = launchToBreakpoint(typeName, breakpointMethodName, 1);
assertDebugViewIsOpen();

return thread;
}

protected ISelection getDebugViewSelection() throws Exception {
return debugView.getViewer().getSelection();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaStackFrame.Category;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
Expand Down Expand Up @@ -226,6 +227,39 @@ public void testWrongSelectionBug540243() throws Exception {
doTestWrongSelection(iterations, typeName, breakpointMethodName, expectedBreakpointHitsCount);
}

public void testStackFrameGrouppingAndColors() throws Exception {
IJavaThread thread = null;
try {
thread = runCodeUntilBreakpoint("StackFrameColoring", "breakpointMethod");
assertNotNull("thread", thread);
var selectedStackFrame = assertStackFrameIsSelected("breakpointMethod");
if (selectedStackFrame == null) {
// skip this test on Mac - see bug 516024
return;
}
sync(() -> {
var allFrames = selectedStackFrame.getParentItem().getItems();
assertNotNull("all frames", allFrames);
assertEquals("frame[0]", "StackFrameColoring.breakpointMethod() line: 34", allFrames[0].getText());
assertEquals("frame[0] - production", IJavaStackFrame.Category.PRODUCTION, getStackFrameCategory(allFrames, 0));
assertEquals("frame[1]", "1 collapsed frames", allFrames[1].getText());
assertTrue("frame[2]", allFrames[2].getText().contains("apply(Object) line: not available"));
assertEquals("frame[2] - production", IJavaStackFrame.Category.PRODUCTION, getStackFrameCategory(allFrames, 2));
assertEquals("frame[3]", "7 collapsed frames", allFrames[3].getText());
assertEquals("frame[4]", "StackFrameColoring.run() line: 29", allFrames[4].getText());
assertEquals("frame[4] - production", IJavaStackFrame.Category.PRODUCTION, getStackFrameCategory(allFrames, 4));
assertEquals("frame[5]", "StackFrameColoring.main(String[]) line: 22", allFrames[5].getText());
assertEquals("frame[5] - production", IJavaStackFrame.Category.PRODUCTION, getStackFrameCategory(allFrames, 5));
});
} finally {
terminateAndCleanUp(thread);
}
}

private Category getStackFrameCategory(TreeItem[] allFrames, int idx) {
return ((IJavaStackFrame) allFrames[idx].getData()).getCategory();
}

/**
* Test for Bug 534319 - Debug View shows wrong information due to threads with short lifetime
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright (c) 2025 Zsombor Gegesy.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Zsombor Gegesy - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.debug.tests.ui;


import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame;

import junit.framework.TestCase;

public class GroupedStackFrameTest extends TestCase {

private GroupedStackFrame groupedStackFrame;

private IJavaStackFrame mockFrame1;
private IJavaStackFrame mockFrame2;

@Override
public void setUp() throws Exception {
groupedStackFrame = new GroupedStackFrame(null);
mockFrame1 = JavaStackFrameMock.createFrame(JavaReferenceTypeMock.createReference("java.util.ArrayList"), false);
mockFrame2 = JavaStackFrameMock.createFrame(JavaReferenceTypeMock.createReference("java.util.LinkedList"), false);
}

public void testAddFrame() {
groupedStackFrame.add(mockFrame1);
assertEquals(1, groupedStackFrame.getFrameCount());

groupedStackFrame.add(mockFrame2);
assertEquals(2, groupedStackFrame.getFrameCount());
}

public void testGetFrameCount() {
assertEquals(0, groupedStackFrame.getFrameCount());

groupedStackFrame.add(mockFrame1);
assertEquals(1, groupedStackFrame.getFrameCount());

groupedStackFrame.add(mockFrame2);
assertEquals(2, groupedStackFrame.getFrameCount());
}

public void testGetFramesAsArray() {
groupedStackFrame.add(mockFrame1);
groupedStackFrame.add(mockFrame2);

Object[] frames = groupedStackFrame.getFramesAsArray(0, 2);
assertNotNull(frames);
assertEquals(2, frames.length);
assertSame(mockFrame1, frames[0]);
assertSame(mockFrame2, frames[1]);

frames = groupedStackFrame.getFramesAsArray(1, 1);
assertNotNull(frames);
assertEquals(1, frames.length);
assertSame(mockFrame2, frames[0]);

frames = groupedStackFrame.getFramesAsArray(2, 1);
assertNull(frames);
}

public void testGetTopMostFrame() {
assertNull(groupedStackFrame.getTopMostFrame());

groupedStackFrame.add(mockFrame1);
assertSame(mockFrame1, groupedStackFrame.getTopMostFrame());

groupedStackFrame.add(mockFrame2);
assertSame(mockFrame1, groupedStackFrame.getTopMostFrame());
}

public void testGetAdapter() {
var adapterType = String.class;

groupedStackFrame.add(mockFrame1);
Object adapter = groupedStackFrame.getAdapter(adapterType);
assertEquals("getAdapter called with class java.lang.String", adapter);

groupedStackFrame = new GroupedStackFrame(null);
adapter = groupedStackFrame.getAdapter(adapterType);
assertNull(adapter);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2025 Zsombor Gegesy.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Zsombor Gegesy - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.debug.tests.ui;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.eclipse.jdt.debug.core.IJavaReferenceType;

/**
* Class to mock {@link IJavaReferenceType}.
*/
class JavaReferenceTypeMock implements InvocationHandler {

final String name;

JavaReferenceTypeMock(String name) {
this.name = name;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getName".equals(method.getName())) {
return name;
}
return null;
}

/**
* Create a new mocked {@link IJavaReferenceType}.
*
* @param name
* @return
*/
public static IJavaReferenceType createReference(String name) {
return (IJavaReferenceType) Proxy.newProxyInstance(JavaReferenceTypeMock.class.getClassLoader(), new Class[] {
IJavaReferenceType.class }, new JavaReferenceTypeMock(name));
}

}
Loading
Loading