From 11980699870d5425eb53d27387f7507c26a23e6a Mon Sep 17 00:00:00 2001 From: Mehmet Emin Karaman Date: Sun, 16 Nov 2025 23:46:21 +0100 Subject: [PATCH] Fixed old regression in ErrorDialogWithToggle from commit 24b179 The change in commit 24b17957ca5367f649a76e1947bd4ceee844b93c reverted the logic of the "don't tell me again" toggle button and only updated JavaHotCodeReplaceListener (which opens HotCodeReplaceErrorDialog). However NoLineNumberAttributesStatusHandler also used ErrorDialogWithToggle but it wasn't updated. This commit fixes "No line attributes error" dialog on breakpoints opened by NoLineNumberAttributesStatusHandler regarding the "Don't tell me again" checkbox behavior. After commit above the value wasn't stored in the preference store. Added regression test to easily reproduce the dialog opening and checking its functionality. Fixes https://github.com/eclipse-jdt/eclipse.jdt.ui/issues/2648 --- .../jdt/debug/tests/AutomatedSuite.java | 8 + .../jdt/debug/tests/TestDebugTarget.java | 154 ++++++++++++++ .../ui/HotCodeReplaceErrorDialogTest.java | 111 +++++++++++ ...LineNumberAttributesStatusHandlerTest.java | 188 ++++++++++++++++++ .../debug/ui/ErrorDialogWithToggle.java | 8 +- .../debug/ui/HotCodeReplaceErrorDialog.java | 1 + 6 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/TestDebugTarget.java create mode 100644 org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/HotCodeReplaceErrorDialogTest.java create mode 100644 org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/NoLineNumberAttributesStatusHandlerTest.java diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java index e2cbcfb1b5..19d44f0d8d 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java @@ -149,7 +149,9 @@ 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.HotCodeReplaceErrorDialogTest; import org.eclipse.jdt.debug.tests.ui.JavaSnippetEditorTest; +import org.eclipse.jdt.debug.tests.ui.NoLineNumberAttributesStatusHandlerTest; import org.eclipse.jdt.debug.tests.ui.OpenFromClipboardTests; import org.eclipse.jdt.debug.tests.ui.ViewManagementTests; import org.eclipse.jdt.debug.tests.ui.VirtualThreadsDebugViewTests; @@ -341,6 +343,12 @@ public AutomatedSuite() { // Scrapbook editor tests addTest(new TestSuite(JavaSnippetEditorTest.class)); + // No Line Number Attributes Status Handler tests + addTest(new TestSuite(NoLineNumberAttributesStatusHandlerTest.class)); + + // Test that ErrorDialogWithToggle Override functionalities won't cause a Stackoverflow Exception + addTest(new TestSuite(HotCodeReplaceErrorDialogTest.class)); + // Debug hover tests addTest(new TestSuite(DebugHoverTests.class)); diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/TestDebugTarget.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/TestDebugTarget.java new file mode 100644 index 0000000000..2aaaf4b1c8 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/TestDebugTarget.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2025 Advantest Corporation 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: + * Advantest Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests; + +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.model.IBreakpoint; +import org.eclipse.debug.core.model.IDebugTarget; +import org.eclipse.debug.core.model.IMemoryBlock; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.debug.core.model.IThread; + +/** + * This is a test debug target which can be used to mimic an IDebugTarget instance. + */ +public class TestDebugTarget implements IDebugTarget { + + @Override + public boolean supportsStorageRetrieval() { + return false; + } + + @Override + public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException { + return null; + } + + @Override + public boolean isDisconnected() { + return false; + } + + @Override + public void disconnect() throws DebugException { + + } + + @Override + public boolean canDisconnect() { + return false; + } + + @Override + public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { + + } + + @Override + public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { + + } + + @Override + public void breakpointAdded(IBreakpoint breakpoint) { + + } + + @Override + public void suspend() throws DebugException { + + } + + @Override + public void resume() throws DebugException { + + } + + @Override + public boolean isSuspended() { + return false; + } + + @Override + public boolean canSuspend() { + return false; + } + + @Override + public boolean canResume() { + return false; + } + + @Override + public void terminate() throws DebugException { + + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean canTerminate() { + return false; + } + + @Override + public T getAdapter(Class adapter) { + return null; + } + + @Override + public String getModelIdentifier() { + return null; + } + + @Override + public ILaunch getLaunch() { + return null; + } + + @Override + public IDebugTarget getDebugTarget() { + return null; + } + + @Override + public boolean supportsBreakpoint(IBreakpoint breakpoint) { + return false; + } + + @Override + public boolean hasThreads() throws DebugException { + return false; + } + + @Override + public IThread[] getThreads() throws DebugException { + return null; + } + + @Override + public IProcess getProcess() { + return null; + } + + @Override + public String getName() throws DebugException { + return null; + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/HotCodeReplaceErrorDialogTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/HotCodeReplaceErrorDialogTest.java new file mode 100644 index 0000000000..8a166a6fdf --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/HotCodeReplaceErrorDialogTest.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2025 Advantest Corporation 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: + * Advantest Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.ui; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.model.IDebugTarget; +import org.eclipse.jdt.debug.tests.TestDebugTarget; +import org.eclipse.jdt.internal.debug.ui.DebugUIMessages; +import org.eclipse.jdt.internal.debug.ui.HotCodeReplaceErrorDialog; +import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants; +import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.widgets.Shell; +import org.junit.Test; + +public class HotCodeReplaceErrorDialogTest extends AbstractDebugUiTests { + + private final class HotCodeReplaceErrorDialogSimRunnable implements Runnable { + private final IStatus status; + private final String toggleMessage; + private final String prefAlertObsoleteMethods; + private final String toggleMessage2; + private final IPreferenceStore preferenceStore; + private final String message; + private final String dialogTitle; + private final Shell shell; + + private HotCodeReplaceErrorDialogSimRunnable(IStatus status, String toggleMessage, String prefAlertObsoleteMethods, String toggleMessage2, IPreferenceStore preferenceStore, String message, String dialogTitle, Shell shell) { + this.status = status; + this.toggleMessage = toggleMessage; + this.prefAlertObsoleteMethods = prefAlertObsoleteMethods; + this.toggleMessage2 = toggleMessage2; + this.preferenceStore = preferenceStore; + this.message = message; + this.dialogTitle = dialogTitle; + this.shell = shell; + } + + @Override + public void run() { + class HotCodeReplaceErrorDialogExtension extends HotCodeReplaceErrorDialog { + private HotCodeReplaceErrorDialogExtension(Shell parentShell, String dialogTitle, String message, IStatus status, String preferenceKey, String toggleMessage, String toggleMessage2, IPreferenceStore store, IDebugTarget target) { + super(parentShell, dialogTitle, message, status, preferenceKey, toggleMessage, toggleMessage2, store, target); + } + + @Override + public void buttonPressed(int id) { + super.buttonPressed(id); + } + } + + HotCodeReplaceErrorDialogExtension errorDialog = new HotCodeReplaceErrorDialogExtension(shell, dialogTitle, message, status, prefAlertObsoleteMethods, toggleMessage, toggleMessage2, preferenceStore, new TestDebugTarget()); + final boolean originalMode = ErrorDialog.AUTOMATED_MODE; + ErrorDialog.AUTOMATED_MODE = false; + try { + errorDialog.create(); + errorDialog.getShell().addShellListener(new ShellAdapter() { + @Override + public void shellActivated(ShellEvent e) { + // To see dialog: processUiEvents(500); + processUiEvents(); + errorDialog.buttonPressed(IDialogConstants.OK_ID); + } + }); + errorDialog.open(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + ErrorDialog.AUTOMATED_MODE = originalMode; + errorDialog.close(); + } + } + } + + public HotCodeReplaceErrorDialogTest(String name) { + super(name); + } + + @Test + public void testHotCodeReplaceErrorDialog() { + Shell shell = JDIDebugUIPlugin.getActiveWorkbenchShell(); + final String vmName = "Dummy VM"; + final String dialogTitle = DebugUIMessages.JDIDebugUIPlugin_Obsolete_methods_remain_1; + final String message = NLS.bind(DebugUIMessages.JDIDebugUIPlugin__0__contains_obsolete_methods_1, new Object[] { vmName }); + final IStatus status = new Status(IStatus.WARNING, JDIDebugUIPlugin.getUniqueIdentifier(), IStatus.WARNING, DebugUIMessages.JDIDebugUIPlugin_Stepping_may_be_hazardous_1, null); + final String toggleMessage = DebugUIMessages.JDIDebugUIPlugin_2; + final String toggleMessage2 = DebugUIMessages.JDIDebugUIPlugin_5; + IPreferenceStore preferenceStore = JDIDebugUIPlugin.getDefault().getPreferenceStore(); + String prefAlertObsoleteMethods = IJDIPreferencesConstants.PREF_ALERT_OBSOLETE_METHODS; + + Runnable dialogSimRunnable = new HotCodeReplaceErrorDialogSimRunnable(status, toggleMessage, prefAlertObsoleteMethods, toggleMessage2, preferenceStore, message, dialogTitle, shell); + sync(dialogSimRunnable); + } +} diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/NoLineNumberAttributesStatusHandlerTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/NoLineNumberAttributesStatusHandlerTest.java new file mode 100644 index 0000000000..4578f8ea07 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/NoLineNumberAttributesStatusHandlerTest.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2025 Advantest Corporation 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: + * Advantest Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.ui; + +import static org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants.PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.ILogListener; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.jdi.internal.ClassTypeImpl; +import org.eclipse.jdi.internal.ReferenceTypeImpl; +import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint; +import org.eclipse.jdt.internal.debug.ui.DebugUIMessages; +import org.eclipse.jdt.internal.debug.ui.ErrorDialogWithToggle; +import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; +import org.eclipse.jdt.internal.debug.ui.NoLineNumberAttributesStatusHandler; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.test.OrderedTestSuite; +import org.eclipse.ui.PlatformUI; +import org.junit.Test; + +/** + * This test is checking the {@link NoLineNumberAttributesStatusHandler} the functionality that the ok button click is saving the state in the + * preference store. + */ +public class NoLineNumberAttributesStatusHandlerTest extends AbstractDebugUiTests { + + public static junit.framework.Test suite() { + return new OrderedTestSuite(NoLineNumberAttributesStatusHandlerTest.class); + } + + private static final class ErrorDialogWithToggleRunnable implements Runnable { + + private final IPreferenceStore preferenceStore; + private final IStatus status; + private final boolean toggleValue; + + private ErrorDialogWithToggleRunnable(IPreferenceStore preferenceStore, IStatus status, boolean toggleValue) { + this.preferenceStore = preferenceStore; + this.status = status; + this.toggleValue = toggleValue; + } + + @Override + public void run() { + Shell shell = PlatformUI.getWorkbench().getModalDialogShellProvider().getShell(); + class ErrorDialogWithToggleForTest extends ErrorDialogWithToggle { + + public ErrorDialogWithToggleForTest(Shell parentShell, String dialogTitle, String message, IStatus status, String preferenceKey, String toggleMessage, IPreferenceStore store) { + super(parentShell, dialogTitle, message, status, preferenceKey, toggleMessage, store); + } + + /** + * Overridden to make public. + */ + @Override + public Button getToggleButton() { + return super.getToggleButton(); + } + + /** + * Overridden to make public. + */ + @Override + public void buttonPressed(int id) { + super.buttonPressed(id); + } + } + ErrorDialogWithToggleForTest dialog = new ErrorDialogWithToggleForTest(shell, DebugUIMessages.NoLineNumberAttributesStatusHandler_Java_Breakpoint_1, NLS.bind(DebugUIMessages.NoLineNumberAttributesStatusHandler_2, "HelloWorld"), status, PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT, DebugUIMessages.NoLineNumberAttributesStatusHandler_3, preferenceStore); + final boolean originalMode = ErrorDialog.AUTOMATED_MODE; + ErrorDialog.AUTOMATED_MODE = false; + try { + dialog.create(); + dialog.getToggleButton().setSelection(toggleValue); + dialog.getShell().addShellListener(new ShellAdapter() { + @Override + public void shellActivated(ShellEvent e) { + // To see dialog: processUiEvents(500); + processUiEvents(); + dialog.buttonPressed(IDialogConstants.OK_ID); + } + }); + dialog.open(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + ErrorDialog.AUTOMATED_MODE = originalMode; + dialog.close(); + } + } + + } + + private static final class ListLogListener implements ILogListener { + private final List loggedEntries; + + private ListLogListener(List loggedEntries) { + this.loggedEntries = loggedEntries; + } + + @Override + public void logging(IStatus status, String plugin) { + if (status.isMultiStatus() && status.getChildren().length == 1) { + loggedEntries.add(status.getChildren()[0]); + } + } + } + + public NoLineNumberAttributesStatusHandlerTest(String name) { + super(name); + } + + @Test + public void testPreferenceSettings() throws Exception { + List loggedEntries = new ArrayList<>(); + ILogListener listener = new ListLogListener(loggedEntries); + + Platform.addLogListener(listener); + try { + IPreferenceStore preferenceStore = JDIDebugUIPlugin.getDefault().getPreferenceStore(); + + IStatus status = new Status(IStatus.ERROR, "org.eclipse.jdt.debug", JavaLineBreakpoint.NO_LINE_NUMBERS, "Teststatus", null); + + // No errors should be logged after "don't tell me" preference is set + boolean dontTellMeAgain = true; + simulateErrorDialogWithToggleExecution(preferenceStore, status, dontTellMeAgain); + assertFalse("Wrong preference set for alert", preferenceStore.getBoolean(PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT)); + assertTrue("Expected no logged entries but got: " + loggedEntries, loggedEntries.isEmpty()); + triggerNoLineAttributesStatusHandler(status); + assertEquals("Expected no logged entries but got: " + loggedEntries, 0, Collections.frequency(loggedEntries, status)); + + // Error should be logged if "don't tell me" preference is not set + dontTellMeAgain = false; + simulateErrorDialogWithToggleExecution(preferenceStore, status, dontTellMeAgain); + assertTrue("Wrong preference set for alert", preferenceStore.getBoolean(PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT)); + assertTrue("Expected no logged entries but got: " + loggedEntries, loggedEntries.isEmpty()); + triggerNoLineAttributesStatusHandler(status); + assertEquals(1, Collections.frequency(loggedEntries, status)); + loggedEntries.clear(); + + // No errors should be logged after "don't tell me" preference is set again + dontTellMeAgain = true; + simulateErrorDialogWithToggleExecution(preferenceStore, status, dontTellMeAgain); + assertFalse("Wrong preference set for alert", preferenceStore.getBoolean(PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT)); + assertTrue("Expected no logged entries but got: " + loggedEntries, loggedEntries.isEmpty()); + triggerNoLineAttributesStatusHandler(status); + assertEquals("Expected no logged entries but got: " + loggedEntries, 0, Collections.frequency(loggedEntries, status)); + } finally { + Platform.removeLogListener(listener); + } + } + + private void triggerNoLineAttributesStatusHandler(IStatus status) { + ReferenceTypeImpl referenceTypeImpl = new ClassTypeImpl(null, null); + referenceTypeImpl.setName("TestRefTypeName"); + NoLineNumberAttributesStatusHandler statusHandler = (NoLineNumberAttributesStatusHandler) DebugPlugin.getDefault().getStatusHandler(status); + statusHandler.handleStatus(status, referenceTypeImpl); + } + + private void simulateErrorDialogWithToggleExecution(IPreferenceStore preferenceStore, IStatus status, boolean toggleValue) throws Exception { + ErrorDialogWithToggleRunnable runnable = new ErrorDialogWithToggleRunnable(preferenceStore, status, toggleValue); + sync(runnable); + } +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/ErrorDialogWithToggle.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/ErrorDialogWithToggle.java index b108fa7f36..4cfee8f5e6 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/ErrorDialogWithToggle.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/ErrorDialogWithToggle.java @@ -105,11 +105,17 @@ protected void buttonPressed(int id, IDebugTarget target) { if (id == IDialogConstants.OK_ID) { // was the OK button pressed? storePreference(target); } + } + + @Override + protected void buttonPressed(int id) { + if (id == IDialogConstants.OK_ID) { // was the OK button pressed? + fStore.setValue(fPreferenceKey, !getToggleButton().getSelection()); + } super.buttonPressed(id); } private void storePreference(IDebugTarget target) { - fStore.setValue(fPreferenceKey, !getToggleButton().getSelection()); if (fToggleButton2 != null) { if (target instanceof JDIDebugTarget jdiTarget) { jdiTarget.setHcrDebugErrorPref(fToggleButton2.getSelection()); diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/HotCodeReplaceErrorDialog.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/HotCodeReplaceErrorDialog.java index 08f67bddef..20d8f876dc 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/HotCodeReplaceErrorDialog.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/HotCodeReplaceErrorDialog.java @@ -153,6 +153,7 @@ public void run() { okPressed(); } else { super.buttonPressed(id, target); + super.buttonPressed(id); } }