Skip to content

Commit 9b366fd

Browse files
committed
debug 2
1 parent 5bb09c1 commit 9b366fd

4 files changed

Lines changed: 163 additions & 34 deletions

File tree

tests/src/main/java/io/openliberty/tools/eclipse/test/it/LibertyPluginSWTBotDebuggerTest.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.disconnectDebugTarget;
1616
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.getDebuggerConnectMenuForDebugObject;
1717
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.getObjectInDebugView;
18+
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.waitForDebuggerConnectMenuState;
1819
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.launchDashboardAction;
1920
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.pressWorkspaceErrorDialogProceedButton;
2021
import static io.openliberty.tools.eclipse.test.it.utils.SWTBotPluginOperations.setBuildCmdPathInPreferences;
@@ -123,8 +124,9 @@ public void testConnectDebuggerMenuCommand() {
123124
Object debugTarget = getObjectInDebugView("Liberty Application Debug");
124125
Assertions.assertNotNull(debugTarget);
125126

126-
// Validate button disabled
127-
Assertions.assertFalse(getDebuggerConnectMenuForDebugObject(launch).isEnabled());
127+
// Validate button disabled — wait up to 5 s for the state to settle
128+
Assertions.assertTrue(waitForDebuggerConnectMenuState(launch, false),
129+
"Connect Liberty Debugger menu should be disabled after debugger connected");
128130

129131
}
130132

@@ -153,8 +155,9 @@ public void testConnectDebuggerMenuCommand_disabledAfterTerminate() {
153155
// Validate application stopped.
154156
LibertyPluginTestUtils.validateLibertyServerStopped(projectPath.toAbsolutePath().toString() + "/target/liberty");
155157

156-
// Validate button disabled
157-
Assertions.assertFalse(getDebuggerConnectMenuForDebugObject(launch).isEnabled());
158+
// Validate button disabled — wait up to 5 s for the state to settle
159+
Assertions.assertTrue(waitForDebuggerConnectMenuState(launch, false),
160+
"Connect Liberty Debugger menu should be disabled after server stopped");
158161
}
159162

160163
/**
@@ -181,8 +184,9 @@ public void testConnectDebuggerMenuCommand_enabledAfterDebuggerDisconnect() {
181184
Object debugTarget = getObjectInDebugView("Liberty Application Debug");
182185
disconnectDebugTarget(debugTarget);
183186

184-
// Validate button enabled
185-
Assertions.assertTrue(getDebuggerConnectMenuForDebugObject(launch).isEnabled());
187+
// Validate button enabled — wait up to 5 s for the state to settle
188+
Assertions.assertTrue(waitForDebuggerConnectMenuState(launch, true),
189+
"Connect Liberty Debugger menu should be enabled after debugger disconnected");
186190
}
187191

188192
/**
@@ -283,10 +287,10 @@ public void testEnhancedDebugMode_disconnectDebuggerRemoveXmlFile() {
283287
Assertions.fail("Xml file not found on " + pathToXmlFile + ".");
284288
}
285289

286-
// Verify button is disabled
290+
// Verify button is disabled — wait up to 5 s for the state to settle
287291
Object launch = getObjectInDebugView(MVN_APP_NAME + " [Liberty]");
288-
SWTBotMenu connectDebuggerMenu = getDebuggerConnectMenuForDebugObject(launch);
289-
Assertions.assertFalse(connectDebuggerMenu.isEnabled());
292+
Assertions.assertTrue(waitForDebuggerConnectMenuState(launch, false),
293+
"Connect Liberty Debugger menu should be disabled when debug mode is active");
290294

291295
// Disconnected Debugger
292296
Object debugTarget = getObjectInDebugView("Liberty Application Debug");

tests/src/main/java/io/openliberty/tools/eclipse/test/it/LibertyPluginSWTBotMultiModMavenTest.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -300,29 +300,33 @@ public void testDebugSourceLookupContentSiblingModule() {
300300

301301
boolean jarEntryFound = false;
302302
boolean warEntryFound = false;
303-
303+
SWTBotTreeItem defaultSourceLookupTree = null;
304+
String foundNodesSnapshot = "";
305+
304306
try {
305307
Object libertyConfigTree = getLibertyTreeItemNoBot(configShell);
306308

307309
context(libertyConfigTree, "New Configuration");
308310

309311
openSourceTab(configShell);
310312

311-
SWTBotTreeItem defaultSourceLookupTree = new SWTBotTreeItem((TreeItem) getDefaultSourceLookupTreeItemNoBot(configShell));
313+
defaultSourceLookupTree = new SWTBotTreeItem((TreeItem) getDefaultSourceLookupTreeItemNoBot(configShell));
314+
foundNodesSnapshot = SWTBotPluginOperations.getTreeItemChildrenAsString(defaultSourceLookupTree);
312315

316+
313317
try {
314318
defaultSourceLookupTree.getNode(MVN_JAR_NAME);
315319
jarEntryFound = true;
316320
} catch (WidgetNotFoundException wnfe) {
317-
// Jar project was not found in source lookup list.
321+
wnfe.printStackTrace();
318322
}
319323

320324
// Lookup war project
321325
try {
322326
defaultSourceLookupTree.getNode(MVN_WAR_NAME);
323327
warEntryFound = true;
324328
} catch (WidgetNotFoundException wnfe) {
325-
// War project was not found in source lookup list.
329+
wnfe.printStackTrace();
326330
}
327331

328332
} finally {
@@ -331,9 +335,11 @@ public void testDebugSourceLookupContentSiblingModule() {
331335

332336
// Validate dependency projects are in source lookup list
333337
Assertions.assertTrue(jarEntryFound,
334-
"The sibling module project, " + MVN_JAR_NAME + ", was not listed in the source lookup list for project " + MVN_APP_NAME);
338+
"The sibling module project, " + MVN_JAR_NAME + ", was not listed in the source lookup list for project " + MVN_APP_NAME
339+
+ ". Nodes found: " + foundNodesSnapshot);
335340
Assertions.assertTrue(warEntryFound,
336-
"The sibling module project, " + MVN_WAR_NAME + ", was not listed in the source lookup list for project " + MVN_APP_NAME);
341+
"The sibling module project, " + MVN_WAR_NAME + ", was not listed in the source lookup list for project " + MVN_APP_NAME
342+
+ ". Nodes found: " + foundNodesSnapshot);
337343

338344
}
339345

@@ -347,6 +353,7 @@ public void testDebugSourceLookupContentParentModule() {
347353

348354
boolean jarEntryFound = false;
349355
boolean warEntryFound = false;
356+
String foundNodesSnapshot = "";
350357

351358
try {
352359
Object libertyConfigTree = getLibertyTreeItemNoBot(configShell);
@@ -356,21 +363,24 @@ public void testDebugSourceLookupContentParentModule() {
356363
openSourceTab(configShell);
357364

358365
SWTBotTreeItem defaultSourceLookupTree = new SWTBotTreeItem((TreeItem) getDefaultSourceLookupTreeItemNoBot(configShell));
359-
366+
foundNodesSnapshot = SWTBotPluginOperations.getTreeItemChildrenAsString(defaultSourceLookupTree);
367+
System.out.println("@ed: Source lookup list. Nodes found: " + foundNodesSnapshot);
368+
360369
// Lookup jar project
361370
try {
362371
defaultSourceLookupTree.getNode(MVN_JAR_NAME);
363372
jarEntryFound = true;
364373
} catch (WidgetNotFoundException wnfe) {
365-
// Jar project was not found in source lookup list.
374+
wnfe.printStackTrace();
375+
366376
}
367377

368378
// Lookup war project
369379
try {
370380
defaultSourceLookupTree.getNode(MVN_WAR_NAME);
371381
warEntryFound = true;
372382
} catch (WidgetNotFoundException wnfe) {
373-
// War project was not found in source lookup list.
383+
wnfe.printStackTrace();
374384
}
375385

376386
} finally {
@@ -379,9 +389,11 @@ public void testDebugSourceLookupContentParentModule() {
379389

380390
// Validate dependency projects are in source lookup list
381391
Assertions.assertTrue(jarEntryFound,
382-
"The child module project, " + MVN_JAR_NAME + ", was not listed in the source lookup list for project " + MVN_PARENT_NAME);
392+
"The child module project, " + MVN_JAR_NAME + ", was not listed in the source lookup list for project " + MVN_PARENT_NAME
393+
+ ". Nodes found: " + foundNodesSnapshot);
383394
Assertions.assertTrue(warEntryFound,
384-
"The child module project, " + MVN_WAR_NAME + ", was not listed in the source lookup list for project " + MVN_PARENT_NAME);
395+
"The child module project, " + MVN_WAR_NAME + ", was not listed in the source lookup list for project " + MVN_PARENT_NAME
396+
+ ". Nodes found: " + foundNodesSnapshot);
385397

386398
}
387399
}

tests/src/main/java/io/openliberty/tools/eclipse/test/it/utils/MagicWidgetFinder.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -236,19 +236,22 @@ public static void context(Object widget, Object... args) {
236236
}
237237

238238
if (!firstAttempt) {
239-
// On retry: dismiss any stale context menu that may still be open, then
240-
// re-select/focus the widget so Eclipse can repopulate dynamic contributions
241-
// (e.g. "Run As" / "Debug As") before we open the menu again.
239+
// On retry: dismiss any stale context menu that may still be open.
242240
dismissOpenContextMenu();
243-
if (widget instanceof TreeItem) {
244-
SWTBotTreeItem ti = (SWTBotTreeItem) obj;
245-
ti.select();
246-
ti.setFocus();
247-
}
248241
pause(2000);
249242
}
250243
firstAttempt = false;
251244

245+
// Always re-select and focus the widget before opening the context menu.
246+
// This ensures the view's selection provider is active so that Eclipse
247+
// populates dynamic contributions (e.g. "Run As" / "Debug As") correctly,
248+
// regardless of whether any other view stole focus since the item was found.
249+
if (widget instanceof TreeItem) {
250+
SWTBotTreeItem ti = (SWTBotTreeItem) obj;
251+
ti.select();
252+
ti.setFocus();
253+
}
254+
252255
try {
253256
for (int x = 0; x < args.length; x++) {
254257
if (x == 0) {

tests/src/main/java/io/openliberty/tools/eclipse/test/it/utils/SWTBotPluginOperations.java

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarPushButton;
5757
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
5858
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
59+
import org.eclipse.ui.IViewPart;
5960
import org.eclipse.ui.IWorkbench;
6061
import org.eclipse.ui.IWorkbenchWindow;
6162
import org.eclipse.ui.PlatformUI;
@@ -151,6 +152,25 @@ public static SWTBotMenu getDebuggerConnectMenuForDebugObject(Object debugObject
151152
return obj.contextMenu("Connect Liberty Debugger");
152153
}
153154

155+
/**
156+
* Polls the input debugger context menu object ("Connect Liberty Debugger") until its
157+
* enabled state matches the expected enabled input indicator.
158+
*
159+
* @param debugObject The debug view tree item to check.
160+
* @param expectedEnabled {@code true} to wait until the menu is enabled;
161+
* {@code false} to wait until it is disabled.
162+
* @return {@code true} if the expected state was reached within 5 seconds.
163+
*/
164+
public static boolean waitForDebuggerConnectMenuState(Object debugObject, boolean expectedEnabled) {
165+
return SWTBotTestCondition.waitFor(() -> {
166+
try {
167+
return getDebuggerConnectMenuForDebugObject(debugObject).isEnabled() == expectedEnabled;
168+
} catch (Exception e) {
169+
return false;
170+
}
171+
}, SWTBotTestCondition.MIN_WAIT_MS);
172+
}
173+
154174
/**
155175
* Disconnects the given debug target (debugger) in the Debug View
156176
*
@@ -793,6 +813,27 @@ public static TreeItem getDefaultSourceLookupTreeItemNoBot(Shell shell) {
793813
return ti;
794814
}
795815

816+
/**
817+
* Returns a comma-separated string of the direct child item texts of the given tree item.
818+
* Uses {@code getItems()} (safe: returns an empty array, never throws) so it can be called
819+
* while the shell is still open without risking an {@code IndexOutOfBoundsException}.
820+
*
821+
* @param treeItem The parent tree item whose children to list.
822+
* @return A string of the form {@code [child1, child2, ...]} or {@code []} if there are none.
823+
*/
824+
public static String getTreeItemChildrenAsString(SWTBotTreeItem treeItem) {
825+
SWTBotTreeItem[] items = treeItem.getItems();
826+
StringBuilder sb = new StringBuilder("[");
827+
for (int i = 0; i < items.length; i++) {
828+
if (i > 0) {
829+
sb.append(", ");
830+
}
831+
sb.append(items[i].getText());
832+
}
833+
sb.append("]");
834+
return sb.toString();
835+
}
836+
796837
public static SWTBotTreeItem getLibertyToolsConfigMenuItem(Shell shell) {
797838
return new SWTBotTreeItem((TreeItem) find(LAUNCH_CONFIG_LIBERTY_MENU_NAME, shell));
798839
}
@@ -968,24 +1009,54 @@ public static void checkRunCleanProjectCheckBox(Shell shell, String runDebugConf
9681009

9691010
public static Object getAppInPackageExplorerTree(String appName) {
9701011
openJavaPerspectiveViaMenu();
971-
// Open Package Explorer view using Eclipse API instead of menu navigation
972-
// This is more reliable in headless CI environments
1012+
// Open Package Explorer view using Eclipse API instead of menu navigation.
1013+
// This is more reliable in headless CI environments.
9731014
showPackageExplorerView();
974-
Object peView = MagicWidgetFinder.findGlobal("Package Explorer");
1015+
1016+
// Obtain the Package Explorer IViewPart directly from the Eclipse API rather
1017+
// than using findGlobal(), which scans all visible shells and could match a
1018+
// widget in an unrelated view. MagicWidgetFinder already knows how to traverse
1019+
// a CommonNavigator (the PE's base class) to reach its TreeItems, so passing
1020+
// the IViewPart as the neighbour is both correct and race-condition-free.
1021+
IViewPart peView = getPackageExplorerView();
9751022

9761023
Object project = MagicWidgetFinder.find(appName, peView, Option.factory().useContains(true).widgetClass(TreeItem.class).build());
977-
go(project);
9781024

979-
// Wait until the tree item is enabled before returning.
1025+
// Select and focus the item so the Package Explorer's selection provider is
1026+
// active before the caller opens a context menu. Calling go() here is omitted
1027+
// intentionally: the intermediate click it triggers can fire selection-listener
1028+
// events that steal focus away before the caller reaches context().
9801029
SWTBotTreeItem botItem = new SWTBotTreeItem((TreeItem) project);
9811030
SWTBotTestCondition.waitFor(botItem::isEnabled, SWTBotTestCondition.SHORT_WAIT_MS);
9821031
botItem.select();
9831032
botItem.setFocus();
984-
System.out.println("Explorer item selected: " + botItem.contextMenu().menuItems());
9851033

1034+
System.out.println("Project in context explorer context menu: " + botItem.contextMenu().menuItems());
9861035
return project;
9871036
}
9881037

1038+
/**
1039+
* Returns the Package Explorer IViewPart obtained directly from the Eclipse API.
1040+
* Must be called after showPackageExplorerView() has confirmed the view is present.
1041+
*
1042+
* @return The Package Explorer IViewPart, or {@code null} if unavailable.
1043+
*/
1044+
private static IViewPart getPackageExplorerView() {
1045+
final IViewPart[] result = { null };
1046+
Display.getDefault().syncExec(() -> {
1047+
try {
1048+
IWorkbench wb = PlatformUI.getWorkbench();
1049+
IWorkbenchWindow window = wb.getActiveWorkbenchWindow();
1050+
if (window != null && window.getActivePage() != null) {
1051+
result[0] = window.getActivePage().findView("org.eclipse.jdt.ui.PackageExplorer");
1052+
}
1053+
} catch (Exception e) {
1054+
System.err.println("Failed to retrieve Package Explorer view: " + e.getMessage());
1055+
}
1056+
});
1057+
return result[0];
1058+
}
1059+
9891060
/**
9901061
* Launches the start action using the debug as configuration shortcut.
9911062
*
@@ -1324,6 +1395,11 @@ public static void openJRETab(SWTWorkbenchBot bot) {
13241395
* Switches the Liberty run configuration main tab to the Source Tab. A Liberty configuration must be opened prior to calling this
13251396
* method.
13261397
*
1398+
* <p>On Linux the source path computer runs asynchronously after a new configuration is created
1399+
* and populates the source lookup tree incrementally. This method waits until the "Default" tree
1400+
* item count has been stable (unchanged) across 3 consecutive polls before returning, ensuring
1401+
* the tree is fully populated before callers inspect its contents.
1402+
*
13271403
* @param shell The Debug Configurations shell already obtained by the caller.
13281404
*/
13291405
public static void openSourceTab(Shell shell) {
@@ -1332,6 +1408,40 @@ public static void openSourceTab(Shell shell) {
13321408
SWTBot shellBot = botShell.bot();
13331409
SWTBotCTabItem tabItem = shellBot.cTabItem("Source");
13341410
tabItem.activate().setFocus();
1411+
1412+
// Wait until the Source tab's "Default" item has a stable non-zero child count
1413+
// across 3 consecutive polls. The source path computer on Linux populates the tree
1414+
// incrementally, so we must confirm the count has stopped changing before returning.
1415+
// We use shellBot.tree(1) to target the Source tab's tree directly rather than
1416+
// find("Default", shell), which can match the wrong tree on the left-hand side of
1417+
// the Debug Configurations dialog.
1418+
final int STABLE_ITERATIONS_REQUIRED = 3;
1419+
final int[] lastCount = { -1 };
1420+
final int[] stableIterations = { 0 };
1421+
SWTBotTestCondition.waitFor(() -> {
1422+
try {
1423+
SWTBotTreeItem defaultItem = shellBot.tree(1).getTreeItem("Default");
1424+
if (defaultItem == null || !defaultItem.isEnabled()) {
1425+
lastCount[0] = -1;
1426+
stableIterations[0] = 0;
1427+
return false;
1428+
}
1429+
defaultItem.expand();
1430+
int count = defaultItem.getItems().length;
1431+
if (count > 0 && count == lastCount[0]) {
1432+
stableIterations[0]++;
1433+
} else {
1434+
// Count changed (or is still zero) — reset the stability counter.
1435+
stableIterations[0] = 0;
1436+
lastCount[0] = count;
1437+
}
1438+
return stableIterations[0] >= STABLE_ITERATIONS_REQUIRED;
1439+
} catch (Exception e) {
1440+
lastCount[0] = -1;
1441+
stableIterations[0] = 0;
1442+
return false;
1443+
}
1444+
}, SWTBotTestCondition.MID_WAIT_MS);
13351445
}
13361446

13371447
/**

0 commit comments

Comments
 (0)