Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/org.eclipse.ui.tests.harness/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Harness Plug-in
Bundle-SymbolicName: org.eclipse.ui.tests.harness;singleton:=true
Bundle-Version: 1.10.600.qualifier
Bundle-Version: 1.10.700.qualifier
Eclipse-BundleShape: dir
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*******************************************************************************
* 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 <[email protected]> - 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:
* <ul>
* <li>prints the test name to the log before and after each test case
* <li>closes windows opened during the test case
* <li>checks for shells unintentionally leaked from the test case
* </ul>
*/
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<IWorkbenchWindow> 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);
trace(TestRunLogUtil.formatTestStartMessage(testName));
}

@Override
public void afterEach(ExtensionContext context) throws Exception {
String testName = getTestName(context);
trace(TestRunLogUtil.formatTestFinishedMessage(testName));

TestWindowListener windowListener = getWindowListener(context);
removeWindowListener(windowListener);

processEvents();
closeAllTestWindows(context);
processEvents();
checkForLeakedShells(context);
}

/**
* Outputs a trace message to the trace output device, if enabled. By default,
* trace messages are sent to <code>System.out</code>.
*
* @param msg the trace message
*/
private static void trace(String msg) {
System.out.println(msg);
}

/**
* 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<IWorkbenchWindow> testWindows = (List<IWorkbenchWindow>) context
.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId()))
.get(TEST_WINDOWS_KEY);

if (testWindows != null) {
List<IWorkbenchWindow> testWindowsCopy = new ArrayList<>(testWindows);
for (IWorkbenchWindow testWindow : testWindowsCopy) {
testWindow.close();
}
testWindows.clear();
}
}

private static class TestWindowListener implements IWindowListener {
private final List<IWorkbenchWindow> testWindows;

public TestWindowListener(List<IWorkbenchWindow> 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<Shell> 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<Shell> initialShells = (Set<Shell>) 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<String> 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 String getTestName(ExtensionContext context) {
return (String) context.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId()))
.get(TEST_NAME_KEY);
}

private TestWindowListener getWindowListener(ExtensionContext context) {
return (TestWindowListener) context
.getStore(ExtensionContext.Namespace.create(getClass(), context.getUniqueId()))
.get(WINDOW_LISTENER_KEY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
* <li>closes windows opened during the test case
* <li>checks for shells unintentionally leaked from the test case
* </ul>
*
* @deprecated Use {@link CloseTestWindowsExtension} with JUnit 5 instead.
* This class is kept for backward compatibility with JUnit 4 tests.
*/
@Deprecated
public class CloseTestWindowsRule extends ExternalResource {

private String testName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,71 +16,26 @@
*******************************************************************************/
package org.eclipse.ui.tests.harness.util;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;

import junit.framework.TestCase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;

/**
* <code>UITestCase</code> is a useful super class for most
* UI tests cases. It contains methods to create new windows
* and pages. It will also automatically close the test
* windows when the tearDown method is called.
*/
public abstract class UITestCase extends TestCase {

/**
* Rule to close windows opened during the test case, manually called to remain
* compatible with JUnit3
*/
private final CloseTestWindowsRule closeTestWindows = new CloseTestWindowsRule();

/**
* Required to preserve the existing logging output when running tests with
* {@link BlockJUnit4ClassRunner}.
*/
@Rule
public TestWatcher testWatcher = new TestWatcher() {
@Override
protected void starting(Description description) {
runningTest = description.getMethodName();
}
@Override
protected void finished(Description description) {
runningTest = null;
}
};
/**
* Name of the currently executed test method. Only valid if test is executed
* with {@link BlockJUnit4ClassRunner}.
*/
private String runningTest = null;

public UITestCase(String testName) {
super(testName);
}
@ExtendWith(CloseTestWindowsExtension.class)
public abstract class UITestCase {

/**
* Simple implementation of setUp. Subclasses are prevented from overriding this
* method to maintain logging consistency. doSetUp() should be overridden
* instead.
* <p>
* This method is public and annotated with {@literal @}{@link Before} to setup
* tests which are configured to {@link RunWith} JUnit4 runner.
* </p>
*/
@Before
@Override
@BeforeEach
public final void setUp() throws Exception {
super.setUp();
String name = runningTest != null ? runningTest : this.getName();
closeTestWindows.setTestName(name);
closeTestWindows.before();
doSetUp();
}

Expand All @@ -98,16 +53,10 @@ protected void doSetUp() throws Exception {
* Simple implementation of tearDown. Subclasses are prevented from overriding
* this method to maintain logging consistency. doTearDown() should be
* overridden instead.
* <p>
* This method is public and annotated with {@literal @}{@link After} to setup
* tests which are configured to {@link RunWith} JUnit4 runner.
* </p>
*/
@After
@Override
@AfterEach
public final void tearDown() throws Exception {
doTearDown();
closeTestWindows.after();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import static org.eclipse.ui.tests.harness.util.UITestUtil.processEvents;
import static org.eclipse.ui.tests.harness.util.UITestUtil.processEventsUntil;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -36,26 +36,28 @@
import org.eclipse.ui.internal.NavigationHistoryAction;
import org.eclipse.ui.intro.IIntroPart;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.tests.harness.util.CloseTestWindowsRule;
import org.eclipse.ui.tests.harness.util.CloseTestWindowsExtension;
import org.eclipse.ui.tests.harness.util.EditorTestHelper;
import org.eclipse.ui.tests.harness.util.FileUtil;
import org.eclipse.ui.tests.harness.util.UITestUtil.Condition;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.TextSelectionNavigationLocation;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
* @since 3.3
*
*/
@ExtendWith(CloseTestWindowsExtension.class)
public class GoBackForwardsTest {

private static final String PROJECT_NAME = "GoBackForwardsTestProject";
private static final String FILE_NAME = "GoBackForwardsTestFile.java";
private static final String FILE_CONTENTS = "public class GoBackForwardsTestFile {\n"
private static final String FILE_CONTENTS = "@ExtendWith(CloseTestWindowsExtension.class)
public class GoBackForwardsTestFile {\n"
+ " public static void main(String[] args) {\n" + " System.out.println(\"Hello world!\");\n"
+ " }\n" + "}";
private static final String GENERIC_EDITOR_ID = "org.eclipse.ui.genericeditor.GenericEditor";
Expand All @@ -65,10 +67,9 @@ public class GoBackForwardsTest {
private IProject project;
private IFile file;

@Rule
public final CloseTestWindowsRule closeTestWindows = new CloseTestWindowsRule();


@Before
@BeforeEach
public void setUp() throws CoreException, IOException {
project = FileUtil.createProject(PROJECT_NAME);
file = FileUtil.createFile(FILE_NAME, project);
Expand Down
Loading
Loading