Skip to content

Commit 1d6b194

Browse files
committed
Bug 574170 - Add coloring to the stack frames of the debug thread view
and add an action to enable/disable colorization from the context menu. All of the categorize can be switched on or off Add a couple of tests for stack frame grouping.
1 parent 62dd506 commit 1d6b194

23 files changed

+1414
-14
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Zsombor Gegesy and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Zsombor Gegesy - initial API and implementation
13+
*******************************************************************************/
14+
15+
import java.util.Arrays;
16+
import java.util.List;
17+
import java.util.stream.Collectors;
18+
19+
public class StackFrameColoring {
20+
21+
public static void main(String[] args) {
22+
new StackFrameColoring().run();
23+
}
24+
25+
void run() {
26+
List<String> result = Arrays.asList("hello", "world").stream().map(value -> {
27+
breakpointMethod();
28+
return value;
29+
}).collect(Collectors.toList());
30+
System.out.println("StackFrameColoring.run called: "+ result);
31+
}
32+
33+
public void breakpointMethod() {
34+
System.out.println("set a breakpoint here");
35+
}
36+
37+
}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation
186186
public static final String CLONE_SUFFIX = "Clone";
187187

188188
final String[] LAUNCH_CONFIG_NAMES_1_4 = { "LargeSourceFile", "LotsOfFields",
189-
"Breakpoints",
189+
"Breakpoints", "StackFrameColoring",
190190
"InstanceVariablesTests",
191191
"LocalVariablesTests", "LocalVariableTests2", "StaticVariablesTests",
192192
"DropTests", "ThrowsNPE", "ThrowsException", "org.eclipse.debug.tests.targets.Watchpoint",
@@ -2971,7 +2971,7 @@ protected void assertNoErrorMarkersExist(IProject[] projects) throws Exception {
29712971
protected void assertNoErrorMarkersExist(IProject project) throws Exception {
29722972
if (project.isAccessible()) {
29732973
IMarker[] projectMarkers = project.findMarkers(null, false, IResource.DEPTH_INFINITE);
2974-
List<IMarker> errorMarkers = Arrays.stream(projectMarkers).filter(marker -> isErrorMarker(marker)).collect(Collectors.toList());
2974+
List<IMarker> errorMarkers = Arrays.stream(projectMarkers).filter(AbstractDebugTest::isErrorMarker).toList();
29752975
String projectErrors = toString(errorMarkers);
29762976
assertEquals("found errors on project " + project + ":" + System.lineSeparator() + projectErrors, Collections.EMPTY_LIST, errorMarkers);
29772977
}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,10 @@
147147
import org.eclipse.jdt.debug.tests.ui.DebugHoverTests;
148148
import org.eclipse.jdt.debug.tests.ui.DebugViewTests;
149149
import org.eclipse.jdt.debug.tests.ui.DetailPaneManagerTests;
150+
import org.eclipse.jdt.debug.tests.ui.GroupedStackFrameTest;
150151
import org.eclipse.jdt.debug.tests.ui.JavaSnippetEditorTest;
151152
import org.eclipse.jdt.debug.tests.ui.OpenFromClipboardTests;
153+
import org.eclipse.jdt.debug.tests.ui.StackFrameGroupingTest;
152154
import org.eclipse.jdt.debug.tests.ui.ViewManagementTests;
153155
import org.eclipse.jdt.debug.tests.ui.VirtualThreadsDebugViewTests;
154156
import org.eclipse.jdt.debug.tests.ui.presentation.ModelPresentationTests;
@@ -244,6 +246,8 @@ public AutomatedSuite() {
244246
addTest(new TestSuite(StepFilterTests.class));
245247
addTest(new TestSuite(StepIntoSelectionTests.class));
246248
addTest(new TestSuite(InstanceFilterTests.class));
249+
addTest(new TestSuite(StackFrameGroupingTest.class));
250+
addTest(new TestSuite(GroupedStackFrameTest.class));
247251
if (JavaProjectHelper.isJava6Compatible()) {
248252
addTest(new TestSuite(ForceReturnTests.class));
249253
}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugViewTests.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,22 +147,22 @@ protected IJavaThread launchToBreakpoint(String typeName, String breakpointMetho
147147
return thread;
148148
}
149149

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

164164
assertEquals("\"breakpointMethod\" should be selected after reaching breakpoint", selectedFrame.getMethodName(), breakpointMethodName);
165-
165+
return selected[0];
166166
}
167167

168168
@Override
@@ -208,7 +208,7 @@ protected void waitForNonConsoleJobs() throws Exception {
208208
}
209209

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

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

236236
protected TreeItem[] getSelectedItemsFromDebugView(boolean wait) throws Exception {
237237
return sync(() -> {
238-
Tree tree = (Tree) debugView.getViewer().getControl();
238+
Tree tree = getDebugViewTree();
239239
TreeItem[] selected = tree.getSelection();
240240
if (!wait) {
241241
return selected;
@@ -254,6 +254,24 @@ protected TreeItem[] getSelectedItemsFromDebugView(boolean wait) throws Exceptio
254254
});
255255
}
256256

257+
private Tree getDebugViewTree() {
258+
return (Tree) debugView.getViewer().getControl();
259+
}
260+
261+
protected IJavaThread runCodeUntilBreakpoint(String typeName, String breakpointMethodName) throws Exception {
262+
sync(() -> getActivePage().hideView(getActivePage().findView(IDebugUIConstants.ID_DEBUG_VIEW)));
263+
264+
waitForNonConsoleJobs();
265+
assertNoErrorMarkersExist();
266+
setPreferenceToShowSystemThreads();
267+
sync(() -> openEditor(typeName + ".java"));
268+
269+
var thread = launchToBreakpoint(typeName, breakpointMethodName, 1);
270+
assertDebugViewIsOpen();
271+
272+
return thread;
273+
}
274+
257275
protected ISelection getDebugViewSelection() throws Exception {
258276
return debugView.getViewer().getSelection();
259277
}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugViewTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import org.eclipse.debug.core.model.IStackFrame;
2020
import org.eclipse.debug.ui.IDebugUIConstants;
2121
import org.eclipse.jdt.debug.core.IJavaStackFrame;
22+
import org.eclipse.jdt.debug.core.IJavaStackFrame.Category;
2223
import org.eclipse.jdt.debug.core.IJavaThread;
2324
import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants;
25+
import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants;
2426
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
27+
import org.eclipse.jdt.internal.debug.ui.StackFrameCategorizer;
2528
import org.eclipse.jface.preference.IPreferenceStore;
2629
import org.eclipse.swt.widgets.TreeItem;
2730
import org.eclipse.test.OrderedTestSuite;
@@ -226,6 +229,41 @@ public void testWrongSelectionBug540243() throws Exception {
226229
doTestWrongSelection(iterations, typeName, breakpointMethodName, expectedBreakpointHitsCount);
227230
}
228231

232+
public void testStackFrameGrouppingAndColors() throws Exception {
233+
IPreferenceStore jdiUIPreferences = JDIDebugUIPlugin.getDefault().getPreferenceStore();
234+
jdiUIPreferences.setValue(IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES, true);
235+
IJavaThread thread = null;
236+
try {
237+
thread = runCodeUntilBreakpoint("StackFrameColoring", "breakpointMethod");
238+
assertNotNull("thread", thread);
239+
var selectedStackFrame = assertStackFrameIsSelected("breakpointMethod");
240+
if (selectedStackFrame == null) {
241+
// skip this test on Mac - see bug 516024
242+
return;
243+
}
244+
sync(() -> {
245+
var allFrames = selectedStackFrame.getParentItem().getItems();
246+
assertNotNull("all frames", allFrames);
247+
assertEquals("frame[0]", "StackFrameColoring.breakpointMethod() line: 34", allFrames[0].getText());
248+
assertEquals("frame[0] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 0));
249+
assertEquals("frame[1]", "1 collapsed frames", allFrames[1].getText());
250+
assertTrue("frame[2]", allFrames[2].getText().contains("apply(Object) line: not available"));
251+
assertEquals("frame[2] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 2));
252+
assertEquals("frame[3]", "7 collapsed frames", allFrames[3].getText());
253+
assertEquals("frame[4]", "StackFrameColoring.run() line: 29", allFrames[4].getText());
254+
assertEquals("frame[4] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 4));
255+
assertEquals("frame[5]", "StackFrameColoring.main(String[]) line: 22", allFrames[5].getText());
256+
assertEquals("frame[5] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 5));
257+
});
258+
} finally {
259+
terminateAndCleanUp(thread);
260+
}
261+
}
262+
263+
private Category getStackFrameCategory(TreeItem[] allFrames, int idx) {
264+
return ((IJavaStackFrame) allFrames[idx].getData()).getCategory();
265+
}
266+
229267
/**
230268
* Test for Bug 534319 - Debug View shows wrong information due to threads with short lifetime
231269
*
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Zsombor Gegesy and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Zsombor Gegesy - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jdt.debug.tests.ui;
15+
16+
17+
import org.eclipse.jdt.debug.core.IJavaStackFrame;
18+
import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame;
19+
20+
import junit.framework.TestCase;
21+
22+
public class GroupedStackFrameTest extends TestCase {
23+
24+
private GroupedStackFrame groupedStackFrame;
25+
26+
private IJavaStackFrame mockFrame1;
27+
private IJavaStackFrame mockFrame2;
28+
29+
@Override
30+
public void setUp() throws Exception {
31+
groupedStackFrame = new GroupedStackFrame(null);
32+
mockFrame1 = JavaStackFrameMock.createFrame(JavaReferenceTypeMock.createReference("java.util.ArrayList"), false);
33+
mockFrame2 = JavaStackFrameMock.createFrame(JavaReferenceTypeMock.createReference("java.util.LinkedList"), false);
34+
}
35+
36+
public void testAddFrame() {
37+
groupedStackFrame.add(mockFrame1);
38+
assertEquals(1, groupedStackFrame.getFrameCount());
39+
40+
groupedStackFrame.add(mockFrame2);
41+
assertEquals(2, groupedStackFrame.getFrameCount());
42+
}
43+
44+
public void testGetFrameCount() {
45+
assertEquals(0, groupedStackFrame.getFrameCount());
46+
47+
groupedStackFrame.add(mockFrame1);
48+
assertEquals(1, groupedStackFrame.getFrameCount());
49+
50+
groupedStackFrame.add(mockFrame2);
51+
assertEquals(2, groupedStackFrame.getFrameCount());
52+
}
53+
54+
public void testGetFramesAsArray() {
55+
groupedStackFrame.add(mockFrame1);
56+
groupedStackFrame.add(mockFrame2);
57+
58+
Object[] frames = groupedStackFrame.getFramesAsArray(0, 2);
59+
assertNotNull(frames);
60+
assertEquals(2, frames.length);
61+
assertSame(mockFrame1, frames[0]);
62+
assertSame(mockFrame2, frames[1]);
63+
64+
frames = groupedStackFrame.getFramesAsArray(1, 1);
65+
assertNotNull(frames);
66+
assertEquals(1, frames.length);
67+
assertSame(mockFrame2, frames[0]);
68+
69+
frames = groupedStackFrame.getFramesAsArray(2, 1);
70+
assertNull(frames);
71+
}
72+
73+
public void testGetTopMostFrame() {
74+
assertNull(groupedStackFrame.getTopMostFrame());
75+
76+
groupedStackFrame.add(mockFrame1);
77+
assertSame(mockFrame1, groupedStackFrame.getTopMostFrame());
78+
79+
groupedStackFrame.add(mockFrame2);
80+
assertSame(mockFrame1, groupedStackFrame.getTopMostFrame());
81+
}
82+
83+
public void testGetAdapter() {
84+
var adapterType = String.class;
85+
86+
groupedStackFrame.add(mockFrame1);
87+
Object adapter = groupedStackFrame.getAdapter(adapterType);
88+
assertEquals("getAdapter called with class java.lang.String", adapter);
89+
90+
groupedStackFrame = new GroupedStackFrame(null);
91+
adapter = groupedStackFrame.getAdapter(adapterType);
92+
assertNull(adapter);
93+
}
94+
95+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Zsombor Gegesy and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Zsombor Gegesy - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jdt.debug.tests.ui;
15+
16+
import java.lang.reflect.InvocationHandler;
17+
import java.lang.reflect.Method;
18+
import java.lang.reflect.Proxy;
19+
20+
import org.eclipse.jdt.debug.core.IJavaReferenceType;
21+
22+
/**
23+
* Class to mock {@link IJavaReferenceType}.
24+
*/
25+
class JavaReferenceTypeMock implements InvocationHandler {
26+
27+
final String name;
28+
29+
JavaReferenceTypeMock(String name) {
30+
this.name = name;
31+
}
32+
33+
@Override
34+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
35+
if ("getName".equals(method.getName())) {
36+
return name;
37+
}
38+
return null;
39+
}
40+
41+
/**
42+
* Create a new mocked {@link IJavaReferenceType}.
43+
*
44+
* @param name
45+
* @return
46+
*/
47+
public static IJavaReferenceType createReference(String name) {
48+
return (IJavaReferenceType) Proxy.newProxyInstance(JavaReferenceTypeMock.class.getClassLoader(), new Class[] {
49+
IJavaReferenceType.class }, new JavaReferenceTypeMock(name));
50+
}
51+
52+
}

0 commit comments

Comments
 (0)