/******************************************************************************* * Copyright (c) 2011, 2015 Wind River Systems, Inc. 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tests; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.Properties; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestListener; import junit.framework.TestResult; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.swt.widgets.Display; import org.eclipse.tcf.te.runtime.interfaces.IConditionTester; import org.eclipse.tcf.te.runtime.utils.Host; import org.eclipse.tcf.te.tests.activator.UIPlugin; import org.eclipse.tcf.te.tests.interfaces.IConfigurationProperties; import org.eclipse.tcf.te.ui.views.interfaces.IUIConstants; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewReference; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.WorkbenchException; import org.osgi.framework.Bundle; /** * Core test case implementation. */ public class CoreTestCase extends TestCase { // Internal property id to store if or if not the views zoom action // got changed before executing the test case. private final static String VIEW_ZOOM_STATE_CHANGED = "viewZoomStateChanged"; //$NON-NLS-1$ // The test configuration private final Properties configuration = new Properties(); // The internal test listener. private final TestListener listener = new InternalTestListener(); /** * Listens to the test executions and logs the failures. */ private class InternalTestListener implements TestListener { /** * Constructor. */ public InternalTestListener() { } /* (non-Javadoc) * @see junit.framework.TestListener#startTest(junit.framework.Test) */ @Override public void startTest(Test test) { } /* (non-Javadoc) * @see junit.framework.TestListener#addError(junit.framework.Test, java.lang.Throwable) */ @Override public synchronized void addError(Test test, Throwable error) { if (test != null && error != null) { // Log the error to the error log. IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(), 1, "Test case '" + test + "' failed with error. Possible cause: " + error.getLocalizedMessage(), //$NON-NLS-1$ //$NON-NLS-2$ error ); UIPlugin.getDefault().getLog().log(status); } } /* (non-Javadoc) * @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError) */ @Override public synchronized void addFailure(Test test, AssertionFailedError failure) { if (test != null && failure != null) { // Log the failure to the error log. IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(), 1, "Test case '" + test + "' failed. Failure: " + failure.getLocalizedMessage(), //$NON-NLS-1$ //$NON-NLS-2$ failure ); UIPlugin.getDefault().getLog().log(status); } } /* (non-Javadoc) * @see junit.framework.TestListener#endTest(junit.framework.Test) */ @Override public void endTest(Test test) { // nothing to do on end test } } /** * Constructor. */ public CoreTestCase() { this(null); } /** * Constructor. * * @param name The test name. */ public CoreTestCase(String name) { super(name); // Setup the test case configuration. Clear out the old configuration. configuration.clear(); initialize(); } /** * Initialize the test configuration. * <p> * Clients may overwrite this method to modify the base configuration. */ protected void initialize() { Assert.isNotNull(configuration); setProperty(VIEW_ZOOM_STATE_CHANGED, false); setProperty(IConfigurationProperties.MAXIMIZE_VIEW, false); setProperty(IConfigurationProperties.TARGET_PERSPECTIVE, "org.eclipse.tcf.te.ui.perspective"); //$NON-NLS-1$ setProperty(IConfigurationProperties.TARGET_VIEW, IUIConstants.ID_EXPLORER); } /** * Sets a boolean configuration property. * * @param key The property key. Must not be <code>null</code>! * @param value The property value. */ protected final void setProperty(String key, boolean value) { Assert.isNotNull(key); setProperty(key, value ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); } /** * Checks if a boolean configuration property has been set to the * given property value. * * @param key The property key. Must not be <code>null</code>! * @param value The property value. * * @return <code>True</code> if the property values match, <code>false</code> if not. */ protected final boolean isProperty(String key, boolean value) { Assert.isNotNull(key); return (value ? Boolean.TRUE : Boolean.FALSE).equals(Boolean.valueOf(configuration.getProperty(key, "false"))); //$NON-NLS-1$ } /** * Sets a string configuration property. * <p> * If the value is <code>null</code>, the configuration property * will be removed. * * @param key The property key. Must not be <code>null</code>! * @param value The property value. */ protected final void setProperty(String key, String value) { Assert.isNotNull(key); if (value != null) configuration.setProperty(key, value); else configuration.remove(key); } /** * Checks if a string configuration property has been set to the given property * value (case insensitive). * <p> * If the value is <code>null</code>, the method returns <code>true</code> if * the configuration property cannot be found. * * @param key The property key. Must not be <code>null</code>! * @param value The property value. * * @return <code>True</code> if the property values match, <code>false</code> if not. */ protected final boolean isProperty(String key, String value) { Assert.isNotNull(key); return value != null ? value.equalsIgnoreCase(configuration.getProperty(key)) : !configuration.containsKey(key); } /** * Returns the value of a string configuration property. * * @param key The property key. Must not be <code>null</code>! * @return The property value or <code>null</code> if not set. */ protected final String getProperty(String key) { Assert.isNotNull(key); return configuration.getProperty(key, null); } /* (non-Javadoc) * @see junit.framework.TestCase#run(junit.framework.TestResult) */ @Override public void run(TestResult result) { if (result != null) result.addListener(listener); super.run(result); if (result != null) result.removeListener(listener); } /* (non-Javadoc) * @see junit.framework.TestCase#runBare() */ @Override public void runBare() throws Throwable { long start = printStart(getName()); boolean toggleInteractiveMode = Host.isInteractive(); if (toggleInteractiveMode) { System.setProperty("NOINTERACTIVE", "true"); //$NON-NLS-1$ //$NON-NLS-2$ try { Field f = Host.class.getDeclaredField("isInteractive"); //$NON-NLS-1$ f.setAccessible(true); f.set(null, null); } catch (Exception e) { /* ignored on purpose */ } } try { super.runBare(); } finally { if (toggleInteractiveMode) { System.setProperty("NOINTERACTIVE", "false"); //$NON-NLS-1$ //$NON-NLS-2$ try { Field f = Host.class.getDeclaredField("isInteractive"); //$NON-NLS-1$ f.setAccessible(true); f.set(null, null); } catch (Exception e) { /* ignored on purpose */ } } printEnd(getName(), start); } } // Local date format presenting long date and time format. private final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.getDefault()); /** * Prints out the test start time. * * @param name The name. Must not be <code>null</code>. * @return The start time in milliseconds. */ protected long printStart(String name) { Assert.isNotNull(name); long startTime = System.currentTimeMillis(); System.out.println("\n=== " + name + " started at: " + DATE_FORMAT.format(new Date(startTime))); //$NON-NLS-1$ //$NON-NLS-2$ return startTime; } /** * Prints out the test end time together with the test duration in milliseconds. * * @param name The name. Must not be <code>null</code>. * @param startTime The start time in milliseconds. */ protected void printEnd(String name, long startTime) { Assert.isNotNull(name); long endTime = System.currentTimeMillis(); long duration = endTime - startTime; System.out.println("=== " + name + " finished at: " + DATE_FORMAT.format(new Date(endTime)) + " (duration: " + duration + " ms)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } /** * Prints out the given message. * * @param message The message. Must not be <code>null</code>. */ protected void printMessage(String message) { Assert.isNotNull(message); System.out.println(message); } /** * Prints out the given debug message. * <p> * Debug messages are printed only if the platform is in debug mode. * * @param message The message. Must not be <code>null</code>. */ protected void printDebugMessage(String message) { Assert.isNotNull(message); if (Platform.inDebugMode()) System.out.println(message); } /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ @Override protected void setUp() throws Exception { super.setUp(); // View handling must run in the UI thread if (Display.findDisplay(Thread.currentThread()) == null) { PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { maximizeView(); } }); } else { maximizeView(); } } /* (non-Javadoc) * @see junit.framework.TestCase#tearDown() */ @Override protected void tearDown() throws Exception { // View handling must run in the UI thread if (Display.findDisplay(Thread.currentThread()) == null) { PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { restoreView(); } }); } else { restoreView(); } flushEventQueue(); super.tearDown(); } /** * Flush the display event queue. * <p> * Unhandled exceptions in the event loop event are caught as follows: * In case multiple events from the event loop throw exceptions these are printed * to stdout. The first exception found in the event loop is thrown to the caller. * * @throws Exception in case an unhandled event loop exception was found. */ protected void flushEventQueue() throws Exception { Display display = Display.getCurrent(); if (display!=null) { //on the dispatch thread already Exception eventLoopException = null; while(!display.isDisposed()) { //loop until event queue is flushed try { if (!display.readAndDispatch()) { break; } } catch(Exception e) { if (eventLoopException==null) { eventLoopException = e; } else { System.out.println("Multiple unhandled event loop exceptions:"); //$NON-NLS-1$ e.printStackTrace(); } } } if (eventLoopException!=null) { throw eventLoopException; } } else { //calling from background thread final Exception[] ex = new Exception[1]; display = Display.getDefault(); display.syncExec(new Runnable() { @Override public void run() { try { flushEventQueue(); } catch(Exception e) { ex[0] = e; } } }); if (ex[0]!=null) throw ex[0]; } } /** * Bring the main view to front. * <p> * If the property {@link IConfigurationProperties#MAXIMIZE_VIEW} is set, the * view will be maximized. * <p> * A possibly open Eclipse Intro View will be hidden automatically. */ protected void maximizeView() { Assert.isNotNull(Display.findDisplay(Thread.currentThread()), "Illegal Thread Access"); //$NON-NLS-1$ final String perspectiveId = getProperty(IConfigurationProperties.TARGET_PERSPECTIVE); assertNotNull("Invalid null-value for test case perspective id!", perspectiveId); //$NON-NLS-1$ final String viewId = getProperty(IConfigurationProperties.TARGET_VIEW); assertNotNull("Invalid null-value for test case view id!", perspectiveId); //$NON-NLS-1$ // Find the Eclipse Intro page and hide it. hideView("org.eclipse.ui.internal.introview", perspectiveId); //$NON-NLS-1$ // Show the main view setProperty(VIEW_ZOOM_STATE_CHANGED, false); IViewPart part = showView(viewId, perspectiveId); assertNotNull("Main view is not available!", part); //$NON-NLS-1$ // Get the view reference for setting the maximized action IViewReference reference = findView(viewId, perspectiveId); assertNotNull("Failed to lookup view reference for main view!", reference); //$NON-NLS-1$ if (reference.getPage().getPartState(reference) != IWorkbenchPage.STATE_MAXIMIZED && isProperty(IConfigurationProperties.MAXIMIZE_VIEW, true)) { reference.getPage().toggleZoom(reference); setProperty(VIEW_ZOOM_STATE_CHANGED, true); } else if (reference.getPage().getPartState(reference) == IWorkbenchPage.STATE_MAXIMIZED && isProperty(IConfigurationProperties.MAXIMIZE_VIEW, false)) { reference.getPage().toggleZoom(reference); setProperty(VIEW_ZOOM_STATE_CHANGED, true); } // Give the UI a chance to repaint if the view zoom action changed if (isProperty(VIEW_ZOOM_STATE_CHANGED, true)) { waitAndDispatch(1000); } } /** * Restore the main view action. */ protected void restoreView() { Assert.isNotNull(Display.findDisplay(Thread.currentThread()), "Illegal Thread Access"); //$NON-NLS-1$ // restore the original view zoom action if (isProperty(VIEW_ZOOM_STATE_CHANGED, true)) { final String perspectiveId = getProperty(IConfigurationProperties.TARGET_PERSPECTIVE); assertNotNull("Invalid null-value for test case perspective id!", perspectiveId); //$NON-NLS-1$ final String viewId = getProperty(IConfigurationProperties.TARGET_VIEW); assertNotNull("Invalid null-value for test case view id!", perspectiveId); //$NON-NLS-1$ IViewReference reference = findView(viewId, perspectiveId); assertNotNull("Failed to lookup view reference for RSE Remote Systems View!", reference); //$NON-NLS-1$ if (reference.getPage().getPartState(reference) == IWorkbenchPage.STATE_MAXIMIZED && isProperty(IConfigurationProperties.MAXIMIZE_VIEW, true)) { reference.getPage().toggleZoom(reference); } else if (reference.getPage().getPartState(reference) != IWorkbenchPage.STATE_MAXIMIZED && isProperty(IConfigurationProperties.MAXIMIZE_VIEW, false)) { reference.getPage().toggleZoom(reference); } setProperty(VIEW_ZOOM_STATE_CHANGED, false); } } /** * Lookup the view reference for the given view and perspective id's. * * @param viewId The view id. Must not be <code>null</code>. * @param perspectiveId The perspective id. Must not be <code>null</code>. * * @return The view reference instance to the view or <code>null</code> if not available. */ public final IViewReference findView(String viewId, String perspectiveId) { Assert.isNotNull(Display.findDisplay(Thread.currentThread()), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(viewId); Assert.isNotNull(perspectiveId); IWorkbench workbench = PlatformUI.getWorkbench(); assertNotNull("Failed to query current workbench instance!", workbench); //$NON-NLS-1$ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); assertNotNull("Failed to query currently active workbench window!", window); //$NON-NLS-1$ try { workbench.showPerspective(perspectiveId, window); } catch (WorkbenchException e) { IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(), "Failed to switch to requested perspective (id = " + perspectiveId + ")!", e); //$NON-NLS-1$ //$NON-NLS-2$ UIPlugin.getDefault().getLog().log(status); } IWorkbenchPage page = window.getActivePage(); assertNotNull("Failed to query currently active workbench page!", page); //$NON-NLS-1$ return page.findViewReference(viewId); } /** * Shows the view. * * @param viewId The view id. Must not be <code>null</code>. * @param perspectiveId The perspective id. Must not be <code>null</code>. * * @return The view part instance to the view or <code>null</code> if it cannot be shown. */ public final IViewPart showView(String viewId, String perspectiveId) { Assert.isNotNull(Display.findDisplay(Thread.currentThread()), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(viewId); Assert.isNotNull(perspectiveId); IWorkbench workbench = PlatformUI.getWorkbench(); assertNotNull("Failed to query current workbench instance!", workbench); //$NON-NLS-1$ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); assertNotNull("Failed to query currently active workbench window!", window); //$NON-NLS-1$ try { workbench.showPerspective(perspectiveId, window); } catch (WorkbenchException e) { IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(), "Failed to switch to requested perspective (id = " + perspectiveId + ")!", e); //$NON-NLS-1$ //$NON-NLS-2$ UIPlugin.getDefault().getLog().log(status); } IWorkbenchPage page = window.getActivePage(); assertNotNull("Failed to query currently active workbench page!", page); //$NON-NLS-1$ IViewPart part = null; try { part = page.showView(viewId); } catch (PartInitException e) { IStatus status = new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(), "Failed to switch to requested perspective (id = " + perspectiveId + ")!", e); //$NON-NLS-1$ //$NON-NLS-2$ UIPlugin.getDefault().getLog().log(status); } return part; } /** * Hides the view. * * @param viewId The view id. Must not be <code>null</code>. * @param perspectiveId The perspective id. Must not be <code>null</code>. */ public final void hideView(String viewId, String perspectiveId) { Assert.isNotNull(Display.findDisplay(Thread.currentThread()), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(viewId); Assert.isNotNull(perspectiveId); IViewReference viewReference = findView(viewId, perspectiveId); if (viewReference != null) { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(viewReference); waitAndDispatch(1000); } } /** * Convenience method to call {@link #waitAndDispatch(long, IConditionTester)}. * * @param timeout The timeout in milliseconds. Must be larger than 0. * * @return <code>True</code> if the method returned because of the timeout, <code>false</code> if the * method returned because of the condition became true. */ public boolean waitAndDispatch(long timeout) { Assert.isTrue(timeout > 0); return waitAndDispatch(timeout, null); } /** * Method does not return until either the timeout has been exceeded or * the interrupt condition is fulfilled. If the method called in a UI thread, * the method keeps the UI thread dispatching running. If called in a * non-UI thread, the method uses {@link Thread#sleep(long)}. * <p> * If a timeout of 0 milliseconds is specified, the method waits until the * given interrupt condition is fulfilled. * * @param timeout The timeout in milliseconds. Must be larger or equals than 0. * @param condition The condition to test. Must not be <code>null</code> if the timeout is 0. * * @return <code>True</code> if the method returned because of the timeout, <code>false</code> if the * method returned because of the condition became true. */ public boolean waitAndDispatch(long timeout, IConditionTester condition) { Assert.isTrue(timeout >= 0); if (timeout == 0) Assert.isNotNull(condition); boolean isTimedOut= false; if (timeout >= 0) { long start = System.currentTimeMillis(); final Display display = Display.findDisplay(Thread.currentThread()); if (display != null) { long current = System.currentTimeMillis(); while (timeout == 0 || (current - start) < timeout && !display.isDisposed()) { if (condition != null && condition.isConditionFulfilled()) break; if (!display.readAndDispatch()) display.sleep(); current = System.currentTimeMillis(); } isTimedOut = (current - start) >= timeout && timeout > 0; } else { long current = System.currentTimeMillis(); while (timeout == 0 || (current - start) < timeout) { if (condition != null && condition.isConditionFulfilled()) break; try { Thread.sleep(50); } catch (InterruptedException e) { /* ignored on purpose */ } current = System.currentTimeMillis(); } isTimedOut = (current - start) >= timeout && timeout > 0; } } if (condition != null) condition.cleanup(); return isTimedOut; } /** * Calculates the absolute path to load additional data from. * <p> * The returned path is calculated as follow:<br> * <ul> * <li>Add org.eclipse.tcf.te.tests bundle location</li> * <li>Add "data"</li> * <li>Add the given relative path</li> * <li>If "hostSpecific" is true, add "Platform.getOS()"</li> * <li>If "archSpecific" is true, add "Platform.getOSArch()"</li> * </ul> * <p> * The calculated path must be a readable directory, otherwise the method will * return <code>null</code>. * * @param path The relative path segment to append. Must not be <code>null</code>. * @param hostSpecific Specify <code>true</code> to include {@link Platform#getOS()}, <code>false</code> if not. * @param archSpecific Specify <code>true</code> to include {@link Platform#getOSArch()}, <code>false</code> if not. * * @return The absolute path to the data location or <code>null</code>. */ protected final IPath getDataLocation(String path, boolean hostSpecific, boolean archSpecific) { Assert.isNotNull(path); IPath root = null; Bundle bundle = getTestBundle(); if (bundle != null) { IPath relative = new Path ("data").append(path); //$NON-NLS-1$ if (hostSpecific) relative = relative.append(Platform.getOS()); if (archSpecific) relative = relative.append(Platform.getOSArch()); URL url = FileLocator.find(bundle, relative, null); if (url != null) { try { root = new Path(FileLocator.resolve(url).getFile()); if (!root.toFile().isDirectory() || !root.toFile().canRead()) { root = null; } } catch (IOException e) { /* ignored on purpose */ } } } return root; } /** * Returns the test bundle. * * @return The test bundle. */ protected Bundle getTestBundle() { return UIPlugin.getDefault().getBundle(); } }