From 74e5b1c58d18c5b5ce4edd7a711a39217690a14c Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Fri, 19 Dec 2025 12:05:19 +0100 Subject: [PATCH] Add CloseTestWindowsExtension for JUnit5 based on CloseTestWindowsRule to JUnit 5 and migrate one test to use it --- .../META-INF/MANIFEST.MF | 2 + .../util/CloseTestWindowsExtension.java | 191 ++++++++++++++++++ .../eclipse/ui/tests/api/IPageLayoutTest.java | 12 +- .../org.eclipse.ui.tests/META-INF/MANIFEST.MF | 5 +- 4 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 tests/org.eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/CloseTestWindowsExtension.java diff --git a/tests/org.eclipse.ui.tests.harness/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.tests.harness/META-INF/MANIFEST.MF index 1d7ae20122e..77654e19a66 100644 --- a/tests/org.eclipse.ui.tests.harness/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.ui.tests.harness/META-INF/MANIFEST.MF @@ -9,6 +9,8 @@ Require-Bundle: org.eclipse.ui;bundle-version="3.208.0", org.junit, org.eclipse.core.resources Bundle-ActivationPolicy: lazy +Import-Package: org.junit.jupiter.api;version="[5.0.0,6.0.0)", + org.junit.jupiter.api.extension;version="[5.0.0,6.0.0)" Export-Package: org.eclipse.ui.tests.harness, org.eclipse.ui.tests.harness.tests, org.eclipse.ui.tests.harness.util, diff --git a/tests/org.eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/CloseTestWindowsExtension.java b/tests/org.eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/CloseTestWindowsExtension.java new file mode 100644 index 00000000000..8703645976b --- /dev/null +++ b/tests/org.eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/CloseTestWindowsExtension.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2000, 2025 IBM 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: + * IBM Corporation - initial API and implementation + * Rolf Theunissen - Bug 553836 (extracted from UITestCase) + *******************************************************************************/ + +package org.eclipse.ui.tests.harness.util; + +import static org.eclipse.ui.tests.harness.util.UITestUtil.processEvents; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWindowListener; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * JUnit 5 Extension for UI tests to clean up windows/shells: + * + */ +public class CloseTestWindowsExtension implements BeforeEachCallback, AfterEachCallback { + + private static final String TEST_NAME_KEY = "testName"; + private static final String TEST_WINDOWS_KEY = "testWindows"; + private static final String WINDOW_LISTENER_KEY = "windowListener"; + private static final String INITIAL_SHELLS_KEY = "initialShells"; + private static final String LEAK_CHECKS_DISABLED_KEY = "leakChecksDisabled"; + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + String testName = context.getDisplayName(); + context.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .put(TEST_NAME_KEY, testName); + + List testWindows = new ArrayList<>(3); + context.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .put(TEST_WINDOWS_KEY, testWindows); + + TestWindowListener windowListener = new TestWindowListener(testWindows); + context.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .put(WINDOW_LISTENER_KEY, windowListener); + + addWindowListener(windowListener); + storeInitialShells(context); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + TestWindowListener windowListener = getWindowListener(context); + removeWindowListener(windowListener); + + processEvents(); + closeAllTestWindows(context); + processEvents(); + checkForLeakedShells(context); + } + + + /** + * Adds a window listener to the workbench to keep track of opened test windows. + */ + private void addWindowListener(TestWindowListener windowListener) { + PlatformUI.getWorkbench().addWindowListener(windowListener); + } + + /** + * Removes the listener. + */ + private void removeWindowListener(TestWindowListener windowListener) { + if (windowListener != null) { + PlatformUI.getWorkbench().removeWindowListener(windowListener); + } + } + + /** + * Close all test windows. + */ + private void closeAllTestWindows(ExtensionContext context) { + @SuppressWarnings("unchecked") + List testWindows = (List) context + .getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .get(TEST_WINDOWS_KEY); + + if (testWindows != null) { + List testWindowsCopy = new ArrayList<>(testWindows); + for (IWorkbenchWindow testWindow : testWindowsCopy) { + testWindow.close(); + } + testWindows.clear(); + } + } + + private static class TestWindowListener implements IWindowListener { + private final List testWindows; + + public TestWindowListener(List testWindows) { + this.testWindows = testWindows; + } + + @Override + public void windowActivated(IWorkbenchWindow window) { + // do nothing + } + + @Override + public void windowDeactivated(IWorkbenchWindow window) { + // do nothing + } + + @Override + public void windowClosed(IWorkbenchWindow window) { + testWindows.remove(window); + } + + @Override + public void windowOpened(IWorkbenchWindow window) { + testWindows.add(window); + } + } + + private void storeInitialShells(ExtensionContext context) { + Set initialShells = Set.of(PlatformUI.getWorkbench().getDisplay().getShells()); + context.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .put(INITIAL_SHELLS_KEY, initialShells); + } + + private void checkForLeakedShells(ExtensionContext context) { + @SuppressWarnings("unchecked") + Set initialShells = (Set) context + .getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .get(INITIAL_SHELLS_KEY); + + Boolean leakChecksDisabled = (Boolean) context + .getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .get(LEAK_CHECKS_DISABLED_KEY); + + if (initialShells == null) { + return; + } + + List leakedModalShellTitles = new ArrayList<>(); + Shell[] shells = PlatformUI.getWorkbench().getDisplay().getShells(); + for (Shell shell : shells) { + if (!shell.isDisposed() && !initialShells.contains(shell)) { + leakedModalShellTitles.add(shell.getText()); + shell.close(); + } + } + + if (leakChecksDisabled == null || !leakChecksDisabled) { + assertEquals(0, leakedModalShellTitles.size(), + "Test leaked modal shell: [" + String.join(", ", leakedModalShellTitles) + "]"); + } + } + + /** + * Disable leak checks for the current test. + * This method should be called from test methods when needed. + */ + public void disableLeakChecks(ExtensionContext context) { + context.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .put(LEAK_CHECKS_DISABLED_KEY, Boolean.TRUE); + } + + + private TestWindowListener getWindowListener(ExtensionContext context) { + return (TestWindowListener) context + .getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId())) + .get(WINDOW_LISTENER_KEY); + } +} diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/IPageLayoutTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/IPageLayoutTest.java index b95c044bf38..63330862ee8 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/IPageLayoutTest.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/IPageLayoutTest.java @@ -14,12 +14,12 @@ package org.eclipse.ui.tests.api; import static org.eclipse.ui.tests.harness.util.UITestUtil.openTestWindow; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.eclipse.ui.tests.harness.util.CloseTestWindowsRule; +import org.eclipse.ui.tests.harness.util.CloseTestWindowsExtension; import org.eclipse.ui.tests.harness.util.EmptyPerspective; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; /** * Test cases for the IPageLayout API. @@ -28,8 +28,8 @@ */ public class IPageLayoutTest { - @Rule - public final CloseTestWindowsRule closeTestWindows = new CloseTestWindowsRule(); + @RegisterExtension + public CloseTestWindowsExtension closeTestWindows = new CloseTestWindowsExtension(); @Test public void testGetDescriptor() { diff --git a/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF index eb7c5781f65..e5ebb9f99ec 100644 --- a/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF @@ -47,9 +47,10 @@ Require-Bundle: org.eclipse.core.resources;bundle-version="3.14.0", org.eclipse.emf.ecore Import-Package: jakarta.annotation, jakarta.inject, - org.osgi.service.event, org.junit.jupiter.api;version="[5.14.0,6.0.0)", - org.junit.platform.suite.api;version="[1.14.0,2.0.0)" + org.junit.jupiter.api.extension;version="[5.14.0,6.0.0)", + org.junit.platform.suite.api;version="[1.14.0,2.0.0)", + org.osgi.service.event Eclipse-AutoStart: true Export-Package: org.eclipse.ui.tests.api, org.eclipse.ui.tests.menus