/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jeanderson Candido <http://jeandersonbc.github.io> - Bug 444070
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 474957
*******************************************************************************/
package org.eclipse.ui.tests.harness.util;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.WorkbenchException;
import junit.framework.TestCase;
/**
* <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 {
/**
* Returns the workbench page input to use for newly created windows.
*
* @return the page input to use for newly created windows
* @since 3.1
*/
public static IAdaptable getPageInput() {
return ResourcesPlugin.getWorkspace().getRoot();
}
class TestWindowListener implements IWindowListener {
private boolean enabled = true;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public void windowActivated(IWorkbenchWindow window) {
// do nothing
}
@Override
public void windowDeactivated(IWorkbenchWindow window) {
// do nothing
}
@Override
public void windowClosed(IWorkbenchWindow window) {
if (enabled)
testWindows.remove(window);
}
@Override
public void windowOpened(IWorkbenchWindow window) {
if (enabled)
testWindows.add(window);
}
}
protected IWorkbench fWorkbench;
private List<IWorkbenchWindow> testWindows;
private TestWindowListener windowListener;
public UITestCase(String testName) {
super(testName);
// ErrorDialog.NO_UI = true;
testWindows = new ArrayList<>(3);
}
/**
* Fails the test due to the given throwable.
*/
public static void fail(String message, Throwable e) {
// If the exception is a CoreException with a multistatus
// then print out the multistatus so we can see all the info.
if (e instanceof CoreException) {
IStatus status = ((CoreException) e).getStatus();
write(status, 0);
} else
e.printStackTrace();
fail(message + ": " + e);
}
private static void indent(OutputStream output, int indent) {
for (int i = 0; i < indent; i++)
try {
output.write("\t".getBytes());
} catch (IOException e) {
// ignore
}
}
private static void write(IStatus status, int indent) {
PrintStream output = System.out;
indent(output, indent);
output.println("Severity: " + status.getSeverity());
indent(output, indent);
output.println("Plugin ID: " + status.getPlugin());
indent(output, indent);
output.println("Code: " + status.getCode());
indent(output, indent);
output.println("Message: " + status.getMessage());
if (status.getException() != null) {
indent(output, indent);
output.print("Exception: ");
status.getException().printStackTrace(output);
}
if (status.isMultiStatus()) {
IStatus[] children = status.getChildren();
for (int i = 0; i < children.length; i++)
write(children[i], indent + 1);
}
}
/**
* Adds a window listener to the workbench to keep track of
* opened test windows.
*/
private void addWindowListener() {
windowListener = new TestWindowListener();
fWorkbench.addWindowListener(windowListener);
}
/**
* Removes the listener added by <code>addWindowListener</code>.
*/
private void removeWindowListener() {
if (windowListener != null) {
fWorkbench.removeWindowListener(windowListener);
}
}
/**
* 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
*/
protected void trace(String msg) {
System.out.println(msg);
}
/**
* Simple implementation of setUp. Subclasses are prevented
* from overriding this method to maintain logging consistency.
* doSetUp() should be overriden instead.
*/
@Override
protected final void setUp() throws Exception {
super.setUp();
fWorkbench = PlatformUI.getWorkbench();
trace("----- " + this.getName()); //$NON-NLS-1$
trace(this.getName() + ": setUp..."); //$NON-NLS-1$
addWindowListener();
doSetUp();
}
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
* The default implementation does nothing.
* Subclasses may extend.
*/
protected void doSetUp() throws Exception {
// do nothing.
}
/**
* Simple implementation of tearDown. Subclasses are prevented
* from overriding this method to maintain logging consistency.
* doTearDown() should be overriden instead.
*/
@Override
protected final void tearDown() throws Exception {
trace(this.getName() + ": tearDown...\n"); //$NON-NLS-1$
removeWindowListener();
doTearDown();
fWorkbench = null;
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
* The default implementation closes all test windows, processing events both before
* and after doing so.
* Subclasses may extend.
*/
protected void doTearDown() throws Exception {
processEvents();
closeAllTestWindows();
processEvents();
}
public static void processEvents() {
Display display = PlatformUI.getWorkbench().getDisplay();
if (display != null)
while (display.readAndDispatch())
;
}
/**
* Utility for waiting until the execution of jobs of any family has
* finished or timeout is reached. If no jobs are running, the method waits
* given minimum wait time. While this method is waiting for jobs, UI events
* are processed.
*
* @param minTimeMs
* minimum wait time in milliseconds
* @param maxTimeMs
* maximum wait time in milliseconds
*/
public static void waitForJobs(long minTimeMs, long maxTimeMs) {
if (maxTimeMs < minTimeMs) {
throw new IllegalArgumentException("Max time is smaller as min time!");
}
final long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < minTimeMs) {
processEvents();
sleep(10);
}
while (!Job.getJobManager().isIdle() && System.currentTimeMillis() - start < maxTimeMs) {
processEvents();
sleep(10);
}
}
/**
* Pauses execution of the current thread
*
* @param millis
*/
protected static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
return;
}
}
/**
* Tries to make given shell active.
*
* <p>
* Note: the method runs at least 1000 milliseconds to make sure the active
* window is really active, see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=417258#c27
*
* @param shell
* non null
* @return true if the given shell is active for the current display
*/
protected boolean forceActive(Shell shell) {
Display display = PlatformUI.getWorkbench().getDisplay();
Shell[] shells = display.getShells();
for (Shell s : shells) {
s.setMinimized(true);
processEvents();
}
waitForJobs(200, 3000);
for (Shell s : shells) {
s.setMinimized(false);
processEvents();
}
waitForJobs(200, 3000);
shell.setVisible(false);
processEvents();
shell.setMinimized(true);
processEvents();
waitForJobs(200, 3000);
shell.setVisible(true);
processEvents();
shell.setMinimized(false);
processEvents();
shell.forceActive();
processEvents();
shell.forceFocus();
processEvents();
waitForJobs(400, 3000);
return display.getActiveShell() == shell;
}
public static class ShellStateListener implements ShellListener {
private AtomicBoolean shellIsActive;
public ShellStateListener(AtomicBoolean shellIsActive) {
this.shellIsActive = shellIsActive;
}
@Override
public void shellIconified(ShellEvent e) {
shellIsActive.set(false);
}
@Override
public void shellDeiconified(ShellEvent e) {
shellIsActive.set(true);
}
@Override
public void shellDeactivated(ShellEvent e) {
shellIsActive.set(false);
}
@Override
public void shellClosed(ShellEvent e) {
shellIsActive.set(false);
}
@Override
public void shellActivated(ShellEvent e) {
shellIsActive.set(true);
}
}
protected static interface Condition {
public boolean compute();
}
/**
*
* @param condition
* , or null if this should only wait
* @param timeout
* , -1 if forever
* @return true if successful, false if time out or interrupted
*/
protected boolean processEventsUntil(Condition condition, long timeout) {
long startTime = System.currentTimeMillis();
Display display = getWorkbench().getDisplay();
while (condition == null || !condition.compute()) {
if (timeout != -1
&& System.currentTimeMillis() - startTime > timeout) {
return false;
}
while (display.readAndDispatch())
;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return true;
}
/**
* Open a test window with the empty perspective.
*/
public IWorkbenchWindow openTestWindow() {
return openTestWindow(EmptyPerspective.PERSP_ID);
}
/**
* Open a test window with the provided perspective.
*/
public IWorkbenchWindow openTestWindow(String perspectiveId) {
try {
IWorkbenchWindow window = fWorkbench.openWorkbenchWindow(
perspectiveId, getPageInput());
waitOnShell(window.getShell());
return window;
} catch (WorkbenchException e) {
fail("Problem opening test window", e);
return null;
}
}
/**
* Try and process events until the new shell is the active shell. This may
* never happen, so time out after a suitable period.
*
* @param shell
* the shell to wait on
* @since 3.2
*/
private void waitOnShell(Shell shell) {
processEvents();
waitForJobs(100, 5000);
}
/**
* Close all test windows.
*/
public void closeAllTestWindows() {
List<IWorkbenchWindow> testWindowsCopy = new ArrayList<>(testWindows);
for (IWorkbenchWindow testWindow : testWindowsCopy) {
testWindow.close();
}
testWindows.clear();
}
/**
* Open a test page with the empty perspective in a window.
*/
public IWorkbenchPage openTestPage(IWorkbenchWindow win) {
IWorkbenchPage[] pages = openTestPage(win, 1);
if (pages != null) {
return pages[0];
}
return null;
}
/**
* Open "n" test pages with the empty perspective in a window.
*/
public IWorkbenchPage[] openTestPage(IWorkbenchWindow win, int pageTotal) {
try {
IWorkbenchPage[] pages = new IWorkbenchPage[pageTotal];
IAdaptable input = getPageInput();
for (int i = 0; i < pageTotal; i++) {
pages[i] = win.openPage(EmptyPerspective.PERSP_ID, input);
}
return pages;
} catch (WorkbenchException e) {
fail("Problem opening test page", e);
return null;
}
}
/**
* Close all pages within a window.
*/
public void closeAllPages(IWorkbenchWindow window) {
IWorkbenchPage[] pages = window.getPages();
for (int i = 0; i < pages.length; i++)
pages[i].close();
}
/**
* Set whether the window listener will manage opening and closing of created windows.
*/
protected void manageWindows(boolean manage) {
windowListener.setEnabled(manage);
}
/**
* Returns the workbench.
*
* @return the workbench
* @since 3.1
*/
protected IWorkbench getWorkbench() {
return fWorkbench;
}
}