diff --git a/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/RuleCheckedStateProvider.java b/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/RuleCheckedStateProvider.java index b8532d5855..3f5d00ff1e 100644 --- a/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/RuleCheckedStateProvider.java +++ b/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/RuleCheckedStateProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -72,8 +72,7 @@ public void checkStateChanged(CheckStateChangedEvent event) { if (element instanceof TriggerRule) { setRuleChecked((TriggerRule) element, event.getChecked()); } else if (element instanceof RuleGroup) { - RuleGroup group = ((RuleGroup) element); - group.getRules().forEach(rule -> setRuleChecked(rule, event.getChecked())); + ((RuleGroup) element).getRules().forEach(rule -> setRuleChecked(rule, event.getChecked())); } } diff --git a/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/TriggerSectionPart.java b/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/TriggerSectionPart.java index d18e3b2f94..1348599058 100644 --- a/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/TriggerSectionPart.java +++ b/application/org.openjdk.jmc.console.ui.notification/src/main/java/org/openjdk/jmc/console/ui/notification/tab/TriggerSectionPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -170,6 +170,8 @@ private void createClient(FormToolkit toolkit) { tree.setData("name", "triggers.RulesTree"); //$NON-NLS-1$ //$NON-NLS-2$ gd = new GridData(SWT.FILL, SWT.FILL, true, true); m_viewer = createViewer(toolkit, tree, client); + tree.setData("org.eclipse.jface.viewer", m_viewer); //$NON-NLS-1$ + tree.setData("viewer", m_viewer); //$NON-NLS-1$ tree.setLayoutData(gd); diff --git a/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java b/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java index 9f6da1517a..f4860ec26c 100644 --- a/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java +++ b/application/tests/org.openjdk.jmc.jolokia.test/src/test/java/org/openjdk/jmc/jolokia/JolokiaTest.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024, 2025, Kantega AS. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Kantega AS. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -112,16 +112,25 @@ public void testReadAttributesOverJolokia() throws MalformedURLException, IOExce .getAttributes()) { String attributeName = attributeInfo.getName(); if (!unsafeAttributes.contains(attributeName)) { - Object attribute = getJolokiaMBeanConnector().getAttribute(objectName, attributeName); - fetched++; - if (attribute instanceof String || attribute instanceof Boolean) { // Assume strings and booleans are safe to compare directly - try { - Object locallyRetrievedAttribute = localConnection.getAttribute(objectName, attributeName); - compared++; - Assert.assertEquals("Comparing returned value of " + objectName + "." + attributeName, - locallyRetrievedAttribute, attribute); - } catch (InstanceNotFoundException e) { - unavailable++; + try { + Object attribute = getJolokiaMBeanConnector().getAttribute(objectName, attributeName); + fetched++; + if (attribute instanceof String || attribute instanceof Boolean) { // Assume strings and booleans are safe to compare directly + try { + Object locallyRetrievedAttribute = localConnection.getAttribute(objectName, + attributeName); + compared++; + Assert.assertEquals("Comparing returned value of " + objectName + "." + attributeName, + locallyRetrievedAttribute, attribute); + } catch (InstanceNotFoundException e) { + unavailable++; + } + } + } catch (RuntimeException e) { + if (isParsingFailure(e)) { + // Skip attributes that cause parsing errors (e.g., NaN values) + } else { + throw e; } } } @@ -160,13 +169,12 @@ private static MBeanServerConnection getJolokiaMBeanConnector() throws IOExcepti @Test public void testDiscover() { boolean isMacOs = "macosx".equals(System.getProperty("osgi.os")); - boolean isCiRun = "true".equals(System.getenv("GITHUB_ACTIONS")); boolean shouldTestMacOS = "true".equals(System.getenv("JOLOKIA_TEST_DISCOVERY_ON_MAC")); - if (isMacOs && isCiRun && !shouldTestMacOS) { - //This does not work in the JMC CI pipeline for Mac + if (isMacOs && !shouldTestMacOS) { + //Multicast discovery does not work on macOS due to network stack limitations // 'D> --> Couldnt send discovery message from /127.0.0.1: java.net.BindException: Can't assign requested address - // D> --> Exception during lookup: java.util.concurrent.ExecutionException: - // org.jolokia.service.discovery.MulticastUtil$CouldntSendDiscoveryPacketException: + // D> --> Exception during lookup: java.util.concurrent.ExecutionException: + // org.jolokia.service.discovery.MulticastUtil$CouldntSendDiscoveryPacketException: // Can't send discovery UDP packet from /127.0.0.1: Can't assign requested address' // We get test coverage on both Linux and Windows return; @@ -192,6 +200,21 @@ public void onDescriptorRemoved(String descriptorId) { Awaitility.await().atMost(Duration.ofSeconds(5)).until(() -> foundVms.get() > 0); } + private static boolean isParsingFailure(RuntimeException e) { + Throwable current = e; + while (current != null) { + if (current instanceof NumberFormatException) { + return true; + } + String message = current.getMessage(); + if (message != null && (message.contains("NaN") || message.contains("NumberFormatException"))) { + return true; + } + current = current.getCause(); + } + return false; + } + @After public void stopListener() throws Exception { if (discoveryListener != null) { diff --git a/application/uitests/org.openjdk.jmc.browser.uitest/src/test/java/org/openjdk/jmc/browser/uitest/ConnectionExportImportTest.java b/application/uitests/org.openjdk.jmc.browser.uitest/src/test/java/org/openjdk/jmc/browser/uitest/ConnectionExportImportTest.java index afdf4518ed..374b59e8d9 100644 --- a/application/uitests/org.openjdk.jmc.browser.uitest/src/test/java/org/openjdk/jmc/browser/uitest/ConnectionExportImportTest.java +++ b/application/uitests/org.openjdk.jmc.browser.uitest/src/test/java/org/openjdk/jmc/browser/uitest/ConnectionExportImportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -132,7 +132,7 @@ public void testImportNonExistantFile() { */ @Test public void testSetMasterPassword() { - MC.jvmBrowser.createConnection("localhost", "0", "username", "Password@123", true, "PasswordConnection"); + MC.jvmBrowser.createConnection("localhost", "0", "username", "Password1!", true, "PasswordConnection"); MC.jvmBrowser.deleteItem("PasswordConnection"); } diff --git a/application/uitests/org.openjdk.jmc.console.uitest/META-INF/MANIFEST.MF b/application/uitests/org.openjdk.jmc.console.uitest/META-INF/MANIFEST.MF index 499c0d82e6..4449129572 100644 --- a/application/uitests/org.openjdk.jmc.console.uitest/META-INF/MANIFEST.MF +++ b/application/uitests/org.openjdk.jmc.console.uitest/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Require-Bundle: org.eclipse.ui, org.openjdk.jmc.rjmx, org.openjdk.jmc.rjmx.ui, org.openjdk.jmc.console.ui.mbeanbrowser, + org.openjdk.jmc.alert, org.junit Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-ActivationPolicy: lazy diff --git a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/ComponentTest.java b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/ComponentTest.java index 5f93d39fbf..f601d7513a 100644 --- a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/ComponentTest.java +++ b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/ComponentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -41,6 +41,7 @@ import org.junit.ClassRule; import org.junit.Test; import org.openjdk.jmc.test.jemmy.MCJemmyTestBase; +import org.openjdk.jmc.test.jemmy.misc.base.wrappers.MCJemmyBase; import org.openjdk.jmc.test.jemmy.MCUITestRule; import org.openjdk.jmc.test.jemmy.misc.helpers.ConnectionHelper; import org.openjdk.jmc.test.jemmy.misc.wrappers.JmxConsole; @@ -87,6 +88,10 @@ public void testTableColumnSorting() { if (mbeanAttributesTree.getColumnIndex("Type", false) != -1) { mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Visible Columns", "Type"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } Assert.assertTrue("The tree contains the \"Type\" column!", mbeanAttributesTree.getColumnIndex("Type", false) == -1); } @@ -95,16 +100,28 @@ public void testTableColumnSorting() { // grouped by type) mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Visible Columns", "Type"); - mbeanAttributesTree.getColumnIndex("Type"); + int typeColumnIndex = mbeanAttributesTree.waitForColumnIndex("Type", 5000); + if (typeColumnIndex == -1) { + Assert.fail("Could not find the column with header \"Type\". Columns: " + + mbeanAttributesTree.getColumnHeaders()); + } // Select an item in the tree and sort on Name - Ascending mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Sort Columns", "Name", "Ascending"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } List namesColumnAscending = getColumn(mbeanAttributesTree.getAllItemTexts(), getColumnIndex("Name")); // Select an item in the tree and sort on Name - Descending mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Sort Columns", "Name", "Descending"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } List namesColumnDescending = getColumn(mbeanAttributesTree.getAllItemTexts(), getColumnIndex("Name")); // Verify that resorting has happened. @@ -118,6 +135,10 @@ public void testTableColumnSorting() { // Select an item in the tree and sort on Value - Ascending mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Sort Columns", "Value", "Ascending"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } Map> valueColumnAscending = getColumnGroupedByType(mbeanAttributesTree.getAllItemTexts(), getColumnIndex("Value"), getColumnIndex("Type")); @@ -125,6 +146,10 @@ public void testTableColumnSorting() { // Select an item in the tree and sort on Value - Descending mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Sort Columns", "Value", "Descending"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } Map> valueColumnDescending = getColumnGroupedByType( mbeanAttributesTree.getAllItemTexts(), getColumnIndex("Value"), getColumnIndex("Type")); @@ -142,6 +167,10 @@ public void testTableColumnSorting() { // Select an item in the tree and sort on Type - Ascending mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Sort Columns", "Type", "Ascending"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } List typeColumnAscending = getColumn(mbeanAttributesTree.getAllItemTexts(), getColumnIndex("Type")); // Verify sorting by Type Assert.assertTrue("Type column not correctly sorted ascending: " + formatForPrinting(typeColumnAscending), @@ -150,12 +179,20 @@ public void testTableColumnSorting() { // Remove the Type column and verify that the table doesn't contain that column (again) mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Visible Columns", "Type"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } Assert.assertTrue("The tree still contains the \"Type\" column!", mbeanAttributesTree.getColumnIndex("Type", false) == -1); // Resort on Name column again, ascending mbeanAttributesTree.select("VmVendor"); mbeanAttributesTree.contextChoose("Sort Columns", "Name", "Ascending"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } namesColumnAscending = getColumn(mbeanAttributesTree.getAllItemTexts(), getColumnIndex("Name")); Assert.assertTrue("Name column not sorted ascending: " + formatForPrinting(namesColumnAscending), listIsSorted(namesColumnAscending, true)); diff --git a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeanBrowserTabTest.java b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeanBrowserTabTest.java index ff1f6cc718..534cc79e16 100644 --- a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeanBrowserTabTest.java +++ b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeanBrowserTabTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -292,6 +292,10 @@ public void testTwoThreadsAtaTime() { MCTree paramsTree = MCTree.getByItem("p0"); paramsTree.select("p0"); paramsTree.enterText("2"); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(500); + } for (int i = 0; i < 2; i++) { paramsTree.select("p0", "[" + i + "]"); paramsTree.enterText(Long.toString(threadIds[i])); diff --git a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeansTest.java b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeansTest.java index 9415460d17..0f1780cfee 100644 --- a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeansTest.java +++ b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/MBeansTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -135,6 +135,9 @@ public void intermittentMBeanTest() { // de-register the MBean mBean2Runner.unregisterMBean(MBEAN_PATH); + // Wait for the subscription to fully stop before checking + sleep(sleepTime); + // verify that the chart isn't updated now Assert.assertFalse( "Chart " + NEW_CHART_DEFAULT_NAME + " is still updated with new data after de-registration of MBean", diff --git a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/TriggersTabTest.java b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/TriggersTabTest.java index 658d63b916..09a3493d13 100644 --- a/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/TriggersTabTest.java +++ b/application/uitests/org.openjdk.jmc.console.uitest/src/test/java/org/openjdk/jmc/console/uitest/TriggersTabTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -72,6 +72,11 @@ public class TriggersTabTest extends MCJemmyTestBase { @Rule public MCUITestRule testRule = new MCUITestRule(verboseRuleOutput) { + @Override + public void before() { + org.openjdk.jmc.alert.AlertPlugin.getDefault().setPopup(true); + } + @Override public void after() { MCTriggersPage.resetTriggerRules(); @@ -97,7 +102,7 @@ public void testApplicationAlert() { Assume.assumeTrue("This feature is only valid on JDK7u4 or later.", ConnectionHelper.is7u4orLater(TEST_CONNECTION)); - MCTriggersPage.createTriggerRule(null, null, "1 s", ALERT_ACTION_NAME, null, ALERT_RULE_GROUP_NAME, + MCTriggersPage.createTriggerRule("1 %", "0 s", "1 s", ALERT_ACTION_NAME, null, ALERT_RULE_GROUP_NAME, ALERT_RULE_NAME + "Application Alert", ALERT_ATTRIBUTE_PATH); // Activate the new rule @@ -121,7 +126,7 @@ public void testDiagnosticCommand() { actionParams.put(DIAGNOSTIC_COMMAND_LOG_FILE_TOOLTIP, filename); actionParams.put(DIAGNOSTIC_COMMAND_TOOLTIP, DIAGNOSTIC_COMMAND); - MCTriggersPage.createTriggerRule(null, null, "1 s", DIAGNOSTIC_COMMAND_ACTION_NAME, actionParams, + MCTriggersPage.createTriggerRule("1 %", "0 s", "1 s", DIAGNOSTIC_COMMAND_ACTION_NAME, actionParams, ALERT_RULE_GROUP_NAME, ALERT_RULE_NAME + "Diagnostic Command", ALERT_ATTRIBUTE_PATH); // Activate the new rule @@ -210,4 +215,5 @@ private boolean verifyFileUpdated(String path, long baseline, int maxWait) { } return updated; } + } diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/MCJemmyTestBase.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/MCJemmyTestBase.java index cd96e8a248..eeda03a5b9 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/MCJemmyTestBase.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/MCJemmyTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -47,6 +47,8 @@ import org.openjdk.jmc.rjmx.servermodel.IServer; import org.openjdk.jmc.rjmx.servermodel.IServerModel; import org.openjdk.jmc.test.TestToolkit; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; import org.openjdk.jmc.test.jemmy.misc.base.wrappers.MCJemmyBase; import org.openjdk.jmc.test.jemmy.misc.helpers.ConnectionHelper; import org.openjdk.jmc.test.jemmy.misc.wrappers.MC; @@ -86,12 +88,33 @@ private static void initialize() { } // Always force focus on Mission Control at initial Jemmy test startup MCJemmyBase.focusMc(); + if (!MC.mcHasFocus() && MCJemmyBase.isOSX()) { + forceActivateShell(); + } Assert.assertTrue("Mission Control did not have focus when Jemmy was initialized.", MC.mcHasFocus()); MC.closeWelcome(); MC.setRecordingAnalysis(false); initialized = true; } + private static void forceActivateShell() { + for (int i = 0; i < 3; i++) { + Display.getDefault().syncExec(() -> { + for (Shell s : Display.getDefault().getShells()) { + if (!s.isDisposed() && s.isVisible()) { + s.forceActive(); + break; + } + } + }); + MCJemmyBase.waitForIdle(); + sleep(500); + if (MC.mcHasFocus()) { + return; + } + } + } + @Rule public MCUITestRule baseTestRule = new MCUITestRule(verboseRuleOutput) { @Override @@ -99,6 +122,9 @@ public void before() { if (!MC.mcHasFocus()) { MCJemmyBase.focusMc(); } + if (!MC.mcHasFocus() && MCJemmyBase.isOSX()) { + forceActivateShell(); + } Assert.assertTrue("Mission Control did not have the focus when the test started.", MC.mcHasFocus()); } @@ -107,6 +133,9 @@ public void after() { if (!MC.mcHasFocus()) { MCJemmyBase.focusMc(); } + if (!MC.mcHasFocus() && MCJemmyBase.isOSX()) { + forceActivateShell(); + } Assert.assertTrue("Mission Control did not have the focus when the test ended.", MC.mcHasFocus()); } }; @@ -122,6 +151,9 @@ public void before() { if (!MC.mcHasFocus()) { MCJemmyBase.focusMc(); } + if (!MC.mcHasFocus() && MCJemmyBase.isOSX()) { + forceActivateShell(); + } Assert.assertTrue("Mission Control did not have focus when the test suite was initialized.", MC.mcHasFocus()); IS_JFR_NEXT = ConnectionHelper.is9u0EAorLater(TEST_CONNECTION); diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/base/wrappers/MCJemmyBase.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/base/wrappers/MCJemmyBase.java index 7e996da46a..b7134f03af 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/base/wrappers/MCJemmyBase.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/base/wrappers/MCJemmyBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -106,7 +106,9 @@ public class MCJemmyBase { public static final long VISIBLE_LOOKUP_DEFAULT_TIMEOUT_MS = 10000; private static final long VISIBLE_LOOKUP_TIMEOUT_MS = Long.getLong("jmc.test.visible.lookup.timeout", VISIBLE_LOOKUP_DEFAULT_TIMEOUT_MS); - private static final int BETWEEN_KEYSTROKES_SLEEP = 100; + private static final int BETWEEN_KEYSTROKES_DEFAULT_SLEEP_MS = 25; + protected static final int BETWEEN_KEYSTROKES_SLEEP = Integer.getInteger("jmc.test.between.keystrokes.sleep", + BETWEEN_KEYSTROKES_DEFAULT_SLEEP_MS); public static final int LOOKUP_SLEEP_TIME_MS = 100; protected static Wrap shell; protected Wrap control; @@ -116,6 +118,7 @@ public class MCJemmyBase { public static final KeyboardButtons CLOSE_BUTTON; public static final KeyboardModifiers SHORTCUT_MODIFIER; public static final String OS_NAME; + private static final boolean IS_OS_X; private static final int IDLE_LOOP_COUNT = 3; private static final int IDLE_LOOP_TIME_STEP = 100; private static final int IDLE_LOOP_TIMEOUT_MS = 10000; @@ -151,18 +154,18 @@ public class MCJemmyBase { Screen.SCREEN.getEnvironment().setInputFactory(new AWTRobotInputFactory()); OS_NAME = System.getProperty("os.name").toLowerCase(); + IS_OS_X = OS_NAME.contains("os x"); SELECTION_BUTTON = OS_NAME.contains("linux") ? KeyboardButtons.SPACE : KeyboardButtons.ENTER; - EXPAND_BUTTON = OS_NAME.contains("os x") ? KeyboardButtons.RIGHT : KeyboardButtons.ADD; - COLLAPSE_BUTTON = OS_NAME.contains("os x") ? KeyboardButtons.LEFT : KeyboardButtons.SUBTRACT; + EXPAND_BUTTON = IS_OS_X ? KeyboardButtons.RIGHT : KeyboardButtons.ADD; + COLLAPSE_BUTTON = IS_OS_X ? KeyboardButtons.LEFT : KeyboardButtons.SUBTRACT; CLOSE_BUTTON = KeyboardButtons.W; - SHORTCUT_MODIFIER = OS_NAME.contains("os x") ? KeyboardModifiers.META_DOWN_MASK - : KeyboardModifiers.CTRL_DOWN_MASK; + SHORTCUT_MODIFIER = IS_OS_X ? KeyboardModifiers.META_DOWN_MASK : KeyboardModifiers.CTRL_DOWN_MASK; Environment.getEnvironment().setProperty(Boolean.class, SWTMenu.SKIPS_DISABLED_PROP, (OS_NAME.contains("windows")) ? false : true); // keyboard re-mapping for Mac OS X with Swedish keyboard - if ("sv".equalsIgnoreCase(InputContext.getInstance().getLocale().getLanguage()) && OS_NAME.contains("os x")) { + if ("sv".equalsIgnoreCase(InputContext.getInstance().getLocale().getLanguage()) && IS_OS_X) { // first making sure that the DefaultCharBindingMap has been loaded and initialized getShell().keyboard(); DefaultCharBindingMap map = (DefaultCharBindingMap) Environment.getEnvironment().getBindingMap(); @@ -551,9 +554,10 @@ public void run() { * origin point of context (right-click) */ private void openContextMenuAtPoint(Point p) { - Display.getDefault().syncExec(() -> { - control.mouse().click(1, p, MouseButtons.BUTTON3); - }); + // Jemmy Robot handles its own threading. Wrapping in syncExec causes + // deadlock on macOS: Robot.waitForIdle -> EventQueue.invokeAndWait + // blocks because AWT needs the main thread which is running the syncExec. + control.mouse().click(1, p, MouseButtons.BUTTON3); } /** @@ -767,6 +771,13 @@ public boolean isWidgetUpdating(int waitTimeMillis) { return isWidgetUpdating(control, waitTimeMillis); } + /** + * @return {@code true} if running on macOS + */ + public static boolean isOSX() { + return IS_OS_X; + } + /** * Waits for background jobs to finish before executing a script line. Since background jobs * post to the UI-thread asynchronously we must ensure they get a chance to run, so we we spin diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JfrUi.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JfrUi.java index 4c5437aa91..388c17ba58 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JfrUi.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JfrUi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -240,6 +240,10 @@ public static EventSettingsData parseEventSettingsTable() { // ensuring that one (any) table item is focused before trying to context choose settingsTable.click(); settingsTable.contextChoose("Visible Columns", END_TIME_COLUMN_HEADER); + MCJemmyBase.waitForIdle(); + if (MCJemmyBase.isOSX()) { + MCJemmyBase.sleep(500); + } } EventSettingsData settings = new EventSettingsData(); diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JvmBrowser.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JvmBrowser.java index 50ed323d21..5dc501bbe2 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JvmBrowser.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/JvmBrowser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -44,6 +44,11 @@ import org.jemmy.TimeoutExpiredException; import org.junit.Assert; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + import org.openjdk.jmc.browser.wizards.ConnectionWizardPage; import org.openjdk.jmc.test.jemmy.MCJemmyTestBase; import org.openjdk.jmc.test.jemmy.misc.base.wrappers.MCJemmyBase; @@ -202,9 +207,24 @@ public void createConnection( waitForIdle(); if (storeCredentials != null && storeCredentials == true) { handleSetMasterPassword(passwd); + waitForIdle(); + } + if (isOSX()) { + boolean found = false; + for (int i = 0; i < 5 && !found; i++) { + if (itemExists(finalPath)) { + found = true; + } else { + sleep(1000); + waitForIdle(); + } + } + Assert.assertTrue("Unable to create item " + Arrays.toString(finalPath) + " from " + Arrays.toString(path), + found); + } else { + Assert.assertTrue("Unable to create item " + Arrays.toString(finalPath) + " from " + Arrays.toString(path), + itemExists(finalPath)); } - Assert.assertTrue("Unable to create item " + Arrays.toString(finalPath) + " from " + Arrays.toString(path), - itemExists(finalPath)); } /** @@ -266,11 +286,45 @@ public void createFolder(String ... path) { * the path of the item to delete */ public void deleteItem(String ... path) { - selectContextOption(ACTION_REMOVE_TEXT, path); - MCDialog delete = new MCDialog(DIALOG_REMOVE_TITLE); + // Check if item exists before attempting deletion + if (!itemExists(path)) { + return; + } + + // Try deletion with retry if dialog doesn't appear + int maxAttempts = MCJemmyBase.isOSX() ? 3 : 2; + MCDialog delete = null; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + if (attempt > 0) { + waitForIdle(); + sleep(1000); + } + + selectContextOption(ACTION_REMOVE_TEXT, path); + + try { + delete = MCDialog.getByAnyDialogTitle(true, DIALOG_REMOVE_TITLE); + if (delete != null) { + break; + } + } catch (Exception e) { + if (attempt == maxAttempts - 1) { + throw e; + } + } + } + + if (delete == null) { + delete = new MCDialog(DIALOG_REMOVE_TITLE); + } + delete.clickButton(MCButton.Labels.YES); waitForIdle(); Assert.assertFalse("Failed deleting", itemExists(path)); + // Mac needs time to recover UI state after deletion before next operation + if (MCJemmyBase.isOSX()) { + sleep(500); + } } /** @@ -431,6 +485,10 @@ public MCDialog dumpRecording(String name, String ... path) { private MCDialog doDumpRecording(String actionName, String ... path) { selectContextOption(actionName, path); + waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(1000); + } return MCDialog.getByAnyDialogTitle(false, DUMP_RECORDING_WIZARD_PAGE_TITLE); } @@ -659,6 +717,10 @@ public void renameFolder(String newName, String ... path) { public void selectContextOption(String option, String ... path) { MCMenu.ensureJvmBrowserVisible(); getTree().select(path); + waitForIdle(); + // Mac needs significantly more time for UI to stabilize after operations + int delay = MCJemmyBase.isOSX() ? 500 : MCJemmyBase.BETWEEN_KEYSTROKES_SLEEP; + sleep(delay); getTree().contextChoose(option); } @@ -735,7 +797,9 @@ public void exportConnections(String fileName, String ... names) { } MCText.getByName(dialog, FileSelector.FILENAME_FIELD_NAME).setText(fileName); MCButton.getByLabel(dialog, MCButton.Labels.FINISH, false).click(); - sleep(1000); + waitForIdle(); + // Mac needs extra time to recover UI state after export operation + sleep(MCJemmyBase.isOSX() ? 3000 : 1000); } /** @@ -774,14 +838,49 @@ public void importConnections(String fileName, Boolean fileExists) { public void handleSetMasterPassword(String password) { MCDialog masterPasswordShell = MCDialog.getByAnyDialogTitle(MasterPasswordWizardPage_SET_MASTER_PASSWORD_TITLE, MasterPasswordWizardPage_VERIFY_MASTER_PASSWORD_TITLE); - if (masterPasswordShell.getText().equals(MasterPasswordWizardPage_SET_MASTER_PASSWORD_TITLE)) { - masterPasswordShell.enterText(Constants.PASSWORD1_FIELD_NAME, password); - masterPasswordShell.enterText(Constants.PASSWORD2_FIELD_NAME, password); + if (isOSX()) { + setPasswordFieldsDirectly(masterPasswordShell, password); } else { - masterPasswordShell.enterText(Constants.PASSWORD1_FIELD_NAME, password); + if (masterPasswordShell.getText().equals(MasterPasswordWizardPage_SET_MASTER_PASSWORD_TITLE)) { + masterPasswordShell.enterText(Constants.PASSWORD1_FIELD_NAME, password); + masterPasswordShell.enterText(Constants.PASSWORD2_FIELD_NAME, password); + } else { + masterPasswordShell.enterText(Constants.PASSWORD1_FIELD_NAME, password); + } } masterPasswordShell.clickButton(MCButton.Labels.OK); sleep(1000); + if (isOSX()) { + Display.getDefault().syncExec(() -> { + for (Shell s : Display.getDefault().getShells()) { + if (!s.isDisposed() && s.isVisible()) { + s.forceActive(); + break; + } + } + }); + waitForIdle(); + } + } + + private void setPasswordFieldsDirectly(MCDialog dialog, String password) { + Display.getDefault().syncExec(() -> { + Shell shell = dialog.getDialogShell().getControl(); + setTextByName(shell, Constants.PASSWORD1_FIELD_NAME, password); + setTextByName(shell, Constants.PASSWORD2_FIELD_NAME, password); + }); + } + + private static void setTextByName(org.eclipse.swt.widgets.Composite parent, String name, String text) { + for (Control child : parent.getChildren()) { + if (child instanceof Text && name.equals(child.getData("name"))) { + ((Text) child).setText(text); + return; + } + if (child instanceof org.eclipse.swt.widgets.Composite) { + setTextByName((org.eclipse.swt.widgets.Composite) child, name, text); + } + } } /** @@ -797,8 +896,23 @@ public void handleSetMasterPassword(String password) { public void connect(boolean valid, String ... path) { MCMenu.ensureJvmBrowserVisible(); String connectionName = path[path.length - 1]; - selectAction(TREE_ITEM_CONSOLE, path); - getTree().contextChoose(ACTION_START_CONSOLE_LABEL); + int retries = MCJemmyBase.isOSX() ? 5 : 1; + for (int attempt = 0; attempt < retries; attempt++) { + selectAction(TREE_ITEM_CONSOLE, path); + waitForIdle(); + if (MCJemmyBase.isOSX()) { + sleep(1000); + } + try { + getTree().contextChoose(ACTION_START_CONSOLE_LABEL); + break; + } catch (RuntimeException e) { + if (attempt == retries - 1) { + throw e; + } + sleep(1000); + } + } if (valid) { if (!ConnectionHelper.is7u40orLater(connectionName)) { try { diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCChartCanvas.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCChartCanvas.java index 5bd8cf8f25..c6a4ab4038 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCChartCanvas.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCChartCanvas.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, 2025, Red Hat Inc. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Red Hat Inc. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -109,6 +109,10 @@ public void clickContextMenuItem(String menuItemText) { StringPopupOwner contextMenu = control.as(StringPopupOwner.class); contextMenu.setPolicy(StringComparePolicy.SUBSTRING); contextMenu.push(getRelativeClickPoint(), new String[] {menuItemText}); + waitForIdle(); + if (isOSX()) { + sleep(500); + } } /** diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCMenu.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCMenu.java index 35b282d91c..3306775faa 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCMenu.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -242,7 +242,7 @@ public static int restoreStackTraceView() { } private static void chooseMenuItem(MenuKeys keys, String[] pathTokens) { - if (OS_NAME.contains("os x")) { + if (isOSX()) { pushKeys(keys); } else { getShell().as(Focusable.class).focuser().focus(); diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTable.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTable.java index 61f710ef5b..ae161927ce 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTable.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -730,7 +730,9 @@ public boolean select(int index, int columnIndex) { int startIndex = control.getProperty(Integer.class, Selectable.STATE_PROP_NAME); if (startIndex == -1) { control.keyboard().pushKey(KeyboardButtons.DOWN); + sleep(BETWEEN_KEYSTROKES_SLEEP); control.keyboard().pushKey(KeyboardButtons.UP); + sleep(BETWEEN_KEYSTROKES_SLEEP); startIndex = control.getProperty(Integer.class, Selectable.STATE_PROP_NAME); } if (startIndex != -1) { @@ -738,10 +740,12 @@ public boolean select(int index, int columnIndex) { KeyboardButtons stepButton = (index > startIndex) ? KeyboardButtons.DOWN : KeyboardButtons.UP; for (int i = 0; i < Math.abs(steps); i++) { control.keyboard().pushKey(stepButton); + sleep(BETWEEN_KEYSTROKES_SLEEP); } // if we have a column > 0 do some side stepping for (int i = 0; i < columnIndex; i++) { control.keyboard().pushKey(KeyboardButtons.RIGHT); + sleep(BETWEEN_KEYSTROKES_SLEEP); } } } @@ -777,6 +781,7 @@ public void selectItems(int start, int end) { for (int i = 0; i < end; i++) { getShell().keyboard().pushKey(KeyboardButtons.DOWN, new KeyboardModifiers[] {KeyboardModifiers.SHIFT_DOWN_MASK}); + sleep(BETWEEN_KEYSTROKES_SLEEP); } } diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCText.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCText.java index b60f8776c8..b5b7ebb28c 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCText.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCText.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.List; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.jemmy.control.Wrap; @@ -417,13 +418,21 @@ public static List getVisible(boolean waitForIdle) { * the text to set for this text widget */ public void setText(String newText) { - SelectionText textField = control.as(SelectionText.class); - click(); - control.keyboard().pushKey(KeyboardButtons.A, SHORTCUT_MODIFIER); - control.keyboard().pushKey(KeyboardButtons.DELETE); - textField.type(newText); - if (isContentAssistPresent()) { - control.keyboard().pushKey(KeyboardButtons.ESCAPE); + if (isOSX()) { + click(); + Display.getDefault().syncExec(() -> { + Text swtText = (Text) control.getControl(); + swtText.setText(newText); + }); + } else { + SelectionText textField = control.as(SelectionText.class); + click(); + control.keyboard().pushKey(KeyboardButtons.A, SHORTCUT_MODIFIER); + control.keyboard().pushKey(KeyboardButtons.DELETE); + textField.type(newText); + if (isContentAssistPresent()) { + control.keyboard().pushKey(KeyboardButtons.ESCAPE); + } } } } diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTree.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTree.java index 2a8f52fa5d..5487089bd0 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTree.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -628,6 +628,52 @@ public void run() { return returnValue; } + /** + * Waits for a column with the specified header to appear with the given timeout + * + * @param columnHeader + * the column header to match + * @param timeout + * timeout in milliseconds + * @return the index of the matching column header, or -1 if not found within timeout + */ + public int waitForColumnIndex(String columnHeader, long timeout) { + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTime < timeout) { + int index = getColumnIndex(columnHeader, false); + if (index != -1) { + return index; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + return -1; + } + + /** + * Returns a list of all column headers in the tree + * + * @return a {@link List} of {@link String} with all column headers + */ + public List getColumnHeaders() { + Fetcher> fetcher = new Fetcher>() { + @Override + public void run() { + List headers = new ArrayList<>(); + for (TreeColumn column : ((Tree) control.as(TreeWrap.class).getControl()).getColumns()) { + headers.add(column.getText()); + } + setOutput(headers); + } + }; + Display.getDefault().syncExec(fetcher); + return fetcher.getOutput(); + } + private Wrap getSelectedItem() { return new ItemWrap<>(control, control.as(TreeWrap.class).getSelectedItem()); } @@ -766,7 +812,7 @@ public void contextChoose(String ... choice) { scrollbarSafeSelection(); Wrap selectedWrap = getSelectedItem(); // workaround (needed on Mac OS X) to make sure that a yellow popup won't disturb during context clicking - if (OS_NAME.contains("os x")) { + if (isOSX()) { selectedWrap.mouse().click(); } StringPopupOwner spo = control.as(StringPopupOwner.class); @@ -850,9 +896,25 @@ public void run() { * the text to set */ public void enterText(String text) { - contextChoose("Change Value"); + int retries = isOSX() ? 3 : 1; + for (int attempt = 0; attempt < retries; attempt++) { + waitForIdle(); + if (isOSX()) { + sleep(500); + } + try { + contextChoose("Change Value"); + break; + } catch (RuntimeException e) { + if (attempt == retries - 1) { + throw e; + } + sleep(1000); + } + } for (int i = 0; i < text.length(); i++) { control.keyboard().typeChar(text.charAt(i)); + sleep(BETWEEN_KEYSTROKES_SLEEP); } // make sure that the text entered is "submitted" before moving focus elsewhere (necessary for Mac) control.keyboard().pushKey(KeyboardButtons.ENTER); @@ -987,30 +1049,96 @@ private Wrap getWrap() { } /** - * Toggles selection state on the currently selected item - * + * Sets the selected item state (checked/unchecked) and fires CheckStateChangedEvent. This + * method fires the event to listeners which may not be triggered by setChecked() alone. + * * @param state * the state to set */ public void setSelectedItemState(boolean state) { if (selectedItemChecked() != state) { - // Ensuring focus on the TreeItem - getSelectedItem().mouse().click(); - // Special case for selecting MenuItem objects in the Export dialog. Linux requires two left keys to set - // focus on the checkbox (within the MenuItem). Also, SPACE is the key to use in both Windows - // and Linux. - if (MCJemmyBase.OS_NAME.contains("linux")) { - getShell().keyboard().pushKey(KeyboardButtons.LEFT); - getShell().keyboard().pushKey(KeyboardButtons.LEFT); - } - getShell().keyboard().pushKey(KeyboardButtons.SPACE); + Fetcher fetcher = new Fetcher() { + @Override + public void run() { + try { + TreeItem item = control.as(TreeWrap.class).getSelectedItem(); + Object data = item.getData(); + + Tree tree = (Tree) control.as(TreeWrap.class).getControl(); + + // Try standard JFace viewer storage keys first + Object viewerObj = tree.getData("org.eclipse.jface.viewer"); + if (viewerObj == null) { + viewerObj = tree.getData("viewer"); + } + + // Fall back to workbench view reflection if not found in tree data + if (viewerObj == null) { + try { + org.eclipse.ui.IWorkbenchWindow window = org.eclipse.ui.PlatformUI.getWorkbench() + .getActiveWorkbenchWindow(); + if (window != null && window.getActivePage() != null) { + org.eclipse.ui.IViewReference[] viewRefs = window.getActivePage() + .getViewReferences(); + for (org.eclipse.ui.IViewReference viewRef : viewRefs) { + org.eclipse.ui.IViewPart part = window.getActivePage() + .findView(viewRef.getId()); + if (part != null) { + java.lang.reflect.Field[] fields = part.getClass().getDeclaredFields(); + for (java.lang.reflect.Field f : fields) { + f.setAccessible(true); + Object fieldValue = f.get(part); + if (fieldValue instanceof org.eclipse.jface.viewers.CheckboxTreeViewer) { + org.eclipse.jface.viewers.CheckboxTreeViewer viewer = (org.eclipse.jface.viewers.CheckboxTreeViewer) fieldValue; + if (viewer.getTree() == tree) { + viewerObj = viewer; + break; + } + } + } + } + if (viewerObj != null) { + break; + } + } + } + } catch (Exception e) { + System.err.println("Viewer lookup via reflection failed: " + e); + } + } + + if (viewerObj instanceof org.eclipse.jface.viewers.CheckboxTreeViewer) { + org.eclipse.jface.viewers.CheckboxTreeViewer viewer = (org.eclipse.jface.viewers.CheckboxTreeViewer) viewerObj; + viewer.setChecked(data, state); + try { + org.eclipse.jface.viewers.CheckStateChangedEvent event = new org.eclipse.jface.viewers.CheckStateChangedEvent( + viewer, data, state); + java.lang.reflect.Method fireMethod = org.eclipse.jface.viewers.CheckboxTreeViewer.class + .getDeclaredMethod("fireCheckStateChanged", + org.eclipse.jface.viewers.CheckStateChangedEvent.class); + fireMethod.setAccessible(true); + fireMethod.invoke(viewer, event); + } catch (Exception fireEx) { + System.err.println("fireCheckStateChanged failed: " + fireEx); + } + } else { + item.setChecked(state); + } + } catch (Exception e) { + System.err.println("Unexpected error setting item state: " + e); + } + setOutput(null); + } + }; + Display.getDefault().syncExec(fetcher); + if (state != selectedItemChecked()) { Assert.fail("Unable to set TreeItem state to: " + state); } } } - private boolean selectedItemChecked() { + public boolean selectedItemChecked() { Fetcher fetcher = new Fetcher() { @Override public void run() { diff --git a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTriggersPage.java b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTriggersPage.java index 2d48f4118c..a457e013db 100644 --- a/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTriggersPage.java +++ b/application/uitests/org.openjdk.jmc.test.jemmy/src/main/java/org/openjdk/jmc/test/jemmy/misc/wrappers/MCTriggersPage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -35,6 +35,8 @@ import java.util.List; import java.util.Map; +import org.junit.Assert; + import org.openjdk.jmc.test.jemmy.misc.base.wrappers.MCJemmyBase; import org.openjdk.jmc.test.jemmy.misc.wrappers.MCButton.Labels; import org.openjdk.jmc.test.jemmy.misc.wrappers.JmxConsole.Tabs; @@ -43,7 +45,7 @@ * The Jemmy wrapper for the Mission Control Triggers console page */ public class MCTriggersPage extends MCJemmyBase { - private static final String SHOW_DIALOG_ON_ALERTS = org.openjdk.jmc.alert.Messages.AlertDialog_POP_UP_ON_ALERTS_TEXT; + public static final String SHOW_DIALOG_ON_ALERTS = org.openjdk.jmc.alert.Messages.AlertDialog_POP_UP_ON_ALERTS_TEXT; private static final String TRIGGER_ALERTS_DIALOG_TITLE = org.openjdk.jmc.alert.Messages.AlertDialog_DIALOG_TITLE; private static final String RULES_TREE_NAME = "triggers.RulesTree"; private static final String LIMIT_PERIOD_TOOLTIP = org.openjdk.jmc.rjmx.triggers.condition.internal.Messages.TriggerCondition_LIMIT_PERIOD_TOOLTIP; @@ -67,6 +69,11 @@ private MCTriggersPage() { public static void toggleTriggerRule(boolean state, String ... path) { selectTriggerRule(path); rulesTree.setSelectedItemState(state); + waitForIdle(); + // Mac needs time for trigger system to register activation and complete first evaluation cycle + if (MCJemmyBase.isOSX()) { + sleep(2500); + } } /** @@ -80,6 +87,19 @@ public static void selectTriggerRule(String ... path) { rulesTree.select(path); } + /** + * Checks if a trigger rule is checked (enabled) + * + * @param path + * the path of the trigger rule + * @return {@code true} if checked, otherwise {@code false} + */ + public static boolean isRuleChecked(String ... path) { + initializeRulesTree(); + rulesTree.select(path); + return rulesTree.selectedItemChecked(); + } + /** * Removes the trigger rule * @@ -102,7 +122,10 @@ public static void removeTriggerRule(String ... path) { * previous alerts */ public static void closeTriggerAlertDialog(boolean cleanUpAlerts) { - MCDialog dialog = new MCDialog(TRIGGER_ALERTS_DIALOG_TITLE); + MCDialog dialog = waitForDialog(TRIGGER_ALERTS_DIALOG_TITLE, 60000); + if (dialog == null) { + Assert.fail("Trigger alert dialog did not appear within 60 seconds"); + } dialog.setButtonState(SHOW_DIALOG_ON_ALERTS, !cleanUpAlerts); if (cleanUpAlerts) { dialog.clickButton("Clear"); @@ -232,12 +255,23 @@ public static void createTriggerRule( Map> actionParams, String ruleGroup, String ruleName, String ... alertAttributePath) { // select the triggers tab JmxConsole.selectTab(Tabs.TRIGGERS); + initializeRulesTree(); + if (rulesTree == null) { + Assert.fail("Triggers rules tree not found."); + } + waitForRulesTreeReady(); // Create a new application alert rule for CPULoad - MCButton.getByLabel("Add...").click(); + MCDialog newRuleDialog = null; + for (int i = 0; i < 3 && newRuleDialog == null; i++) { + MCButton.getByLabel("Add...").click(); + newRuleDialog = waitForDialog("Add New Rule", 30000); + } + if (newRuleDialog == null) { + Assert.fail("Failed to open Add New Rule dialog after retries."); + } // first page - MCDialog newRuleDialog = new MCDialog("Add New Rule"); MCTree attributeTree = MCTree.getFirst(newRuleDialog); attributeTree.select(alertAttributePath); newRuleDialog.clickButton(Labels.NEXT); @@ -282,6 +316,31 @@ public static void createTriggerRule( newRuleDialog.closeWithButton(Labels.FINISH); } + private static void waitForRulesTreeReady() { + if (rulesTree == null) { + Assert.fail("Triggers rules tree not initialized."); + } + for (int i = 0; i < 20; i++) { + int count = rulesTree.getDirectChildItemsCount(); + if (count > 0) { + return; + } + sleep(500); + } + } + + private static MCDialog waitForDialog(String title, long timeoutMs) { + long deadline = System.currentTimeMillis() + timeoutMs; + while (System.currentTimeMillis() < deadline) { + MCDialog dialog = MCDialog.getByAnyDialogTitle(true, true, title); + if (dialog != null) { + return dialog; + } + sleep(500); + } + return null; + } + private static void initializeRulesTree() { JmxConsole.selectTab(JmxConsole.Tabs.TRIGGERS); rulesTree = MCTree.getFirstVisibleByName(MCTriggersPage.RULES_TREE_NAME);