Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'tests/junit/plugins/junit/org.eclipse.papyrus.junit.framework/src/org/eclipse/papyrus/junit/framework/classification/ClassificationRunnerImpl.java')
-rw-r--r--tests/junit/plugins/junit/org.eclipse.papyrus.junit.framework/src/org/eclipse/papyrus/junit/framework/classification/ClassificationRunnerImpl.java346
1 files changed, 346 insertions, 0 deletions
diff --git a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.framework/src/org/eclipse/papyrus/junit/framework/classification/ClassificationRunnerImpl.java b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.framework/src/org/eclipse/papyrus/junit/framework/classification/ClassificationRunnerImpl.java
new file mode 100644
index 00000000000..f83dd28819e
--- /dev/null
+++ b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.framework/src/org/eclipse/papyrus/junit/framework/classification/ClassificationRunnerImpl.java
@@ -0,0 +1,346 @@
+/*****************************************************************************
+ * Copyright (c) 2014, 2016 CEA LIST, Christian W. Damus, 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:
+ * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
+ * Christian W. Damus (CEA) - add support for conditional tests
+ * Christian W. Damus (CEA) - bug 432813
+ * Christian W. Damus (CEA) - bug 434993
+ * Christian W. Damus (CEA) - bug 436047
+ * Christian W. Damus - bug 485156
+ *
+ *****************************************************************************/
+package org.eclipse.papyrus.junit.framework.classification;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.commands.operations.DefaultOperationHistory;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.viewers.BaseLabelProvider;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.LabelProviderChangedEvent;
+import org.eclipse.papyrus.infra.tools.util.ListHelper;
+import org.eclipse.papyrus.junit.framework.classification.rules.ConditionRule;
+import org.eclipse.papyrus.junit.framework.classification.rules.Conditional;
+import org.eclipse.papyrus.junit.framework.classification.rules.MemoryLeakRule;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+/**
+ * Internal implementation of the common classification-sensitive behaviour
+ * of the {@link ClassificationRunner} and {@link ClassificationRunnerWithParameters}
+ * test runners.
+ *
+ * @author Camille Letavernier
+ */
+class ClassificationRunnerImpl {
+
+ private final static long EVENT_LOOP_TIMEOUT = 2L * 60L * 1000L; // 2 minutes in millis
+
+ private final static long ONE_MB = 1024L * 1024L; // a megabyte, in bytes
+
+ private static final Supplier<TestRule> uiFlusherRuleSupplier = createUIFlusherRuleSupplier();
+
+ private final ThreadLocal<Object> preparedTest = new ThreadLocal<Object>();
+
+ private final Delegate delegate;
+
+ ClassificationRunnerImpl(Delegate delegate) throws InitializationError {
+ super();
+
+ this.delegate = delegate;
+ }
+
+ final void runChild(FrameworkMethod method, RunNotifier notifier) {
+ List<Annotation> allAnnotations = ListHelper.asList(method.getAnnotations());
+ allAnnotations.addAll(Arrays.asList(method.getMethod().getDeclaringClass().getAnnotations()));
+ if (ClassificationConfig.shouldRun(allAnnotations.toArray(new Annotation[allAnnotations.size()])) && conditionSatisfied(method)) {
+ delegate.runChild(method, notifier);
+ } else {
+ Description description = delegate.describeChild(method);
+ notifier.fireTestIgnored(description);
+ }
+ }
+
+ final Object createTest() throws Exception {
+ // Look for a prepared test instance
+ Object result = preparedTest.get();
+ if (result != null) {
+ // We won't need this test instance again
+ clearPreparedTest();
+ } else {
+ result = delegate.createTest();
+ }
+
+ return result;
+ }
+
+ final Object prepareTest() throws Exception {
+ // Prepare the test instance and stash it to return on the next invocation
+ Object result = delegate.createTest();
+ preparedTest.set(result);
+ return result;
+ }
+
+ final void clearPreparedTest() {
+ preparedTest.remove();
+ }
+
+ private boolean conditionSatisfied(FrameworkMethod method) {
+ boolean result = true;
+
+ // Does this test declare some precondition?
+ Conditional conditional = method.getAnnotation(Conditional.class);
+ if (conditional != null) {
+ try {
+ // We need the test instance to invoke the condition on it, so prepare it now
+ Object test = prepareTest();
+ result = ConditionRule.testCondition(method.getMethod().getDeclaringClass(), conditional, test);
+ } catch (Throwable t) {
+ // If we couldn't create the test, then we should just ignore it
+ result = false;
+ } finally {
+ if (!result) {
+ // We won't be running the test, so forget the prepared instance (if any)
+ clearPreparedTest();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ List<TestRule> getTestRules(Object target) {
+ // MemoryLeakRules must be the outer-most rules, because leak assertions must only happen after all possible tear-down actions have run
+ return reorderForMemoryLeakRules(delegate.getTestRules(target));
+ }
+
+ private List<TestRule> reorderForMemoryLeakRules(List<TestRule> rules) {
+ // Quick scan for memory rules
+ if (!rules.isEmpty()) {
+ int memoryRuleCount = Iterables.size(Iterables.filter(rules, Predicates.instanceOf(MemoryLeakRule.class)));
+ if (memoryRuleCount > 0) {
+ // Bubble the memory rules to the end
+ int limit = rules.size() - memoryRuleCount;
+
+ for (int i = 0; i < limit; i++) {
+ if (rules.get(i) instanceof MemoryLeakRule) {
+ // Move the rule to the end and take a step back to get the next element
+ rules.add(rules.remove(i--));
+ }
+ }
+ }
+ }
+
+ return rules;
+ }
+
+ Statement classBlock(RunNotifier notifier) {
+ Statement result = delegate.classBlock(notifier);
+
+ // Wrap the class suite in a rule that flushes the UI thread to release memory referenced by UI runnables
+ TestRule uiFlusher = uiFlusherRuleSupplier.get();
+ if (uiFlusher != null) {
+ // This rule doesn't need any actual test description
+ result = uiFlusher.apply(result, Description.EMPTY);
+ }
+
+ return result;
+ }
+
+ private static Supplier<TestRule> createUIFlusherRuleSupplier() {
+ Supplier<TestRule> result = Suppliers.ofInstance(null);
+
+ try {
+ if (PlatformUI.isWorkbenchRunning()) {
+ result = Suppliers.memoize(new Supplier<TestRule>() {
+
+ @Override
+ public TestRule get() {
+ if (Display.getCurrent() != null) {
+ return new TestWatcher() {
+
+ @Override
+ protected void finished(Description description) {
+ final Display display = Display.getCurrent();
+ if (display == null) {
+ // Can't do UI manipulations and history listener hacking except on the UI thread
+ return;
+ }
+
+ flushUIEventQueue(display);
+
+ purgeZombieHistoryListeners();
+
+ clearDecorationScheduler();
+ }
+ };
+ }
+
+ return null;
+ }
+ });
+ }
+ } catch (LinkageError e) {
+ // Not running in Eclipse UI context. Fine
+ }
+
+ return result;
+ }
+
+ private static void flushUIEventQueue(Display display) {
+ long base = System.currentTimeMillis();
+ long timeout = EVENT_LOOP_TIMEOUT;
+
+ // Flush the UI thread's pending events
+ while (!display.isDisposed()) {
+ try {
+ if (!display.readAndDispatch()) {
+ break;
+ }
+ } catch (Exception e) {
+ // Ignore it
+ }
+
+ long now = System.currentTimeMillis();
+ if ((now - base) > timeout) {
+ // This seems to be taking a really long time. What's up?
+ base = now;
+ timeout = timeout * 3L / 2L; // Exponential back-off to avoid over-reporting
+ int freeMB = (int) (Runtime.getRuntime().freeMemory() / ONE_MB);
+ System.err.printf("========%nUI event queue clean-up seems to be running long.%nCurrent free memory: %d MB%n========%n%n", freeMB);
+ }
+ }
+ }
+
+ private static void purgeZombieHistoryListeners() {
+ // If there are no editors open any longer, then all of the action handlers currently
+ // listening to the operation history are leaked, so remove them. This ensures that we
+ // do not end up wasting time in notifying thousands of dead/broken/useless listeners
+ // every time a test case executes an operation on the history (which happens *a lot*)
+ IWorkbench bench = PlatformUI.getWorkbench();
+ IWorkbenchWindow window = (bench == null) ? null : bench.getActiveWorkbenchWindow();
+ if ((window == null) && (bench != null) && (bench.getWorkbenchWindowCount() > 0)) {
+ window = bench.getWorkbenchWindows()[0];
+ }
+ if (window != null && window.getActivePage().getEditorReferences().length == 0) {
+ final ListenerList historyListeners = OperationHistoryHelper.getOperationHistoryListeners();
+ final Object[] listeners = historyListeners.getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ if (OperationHistoryHelper.shouldRemoveHistoryListener(listeners[i])) {
+ historyListeners.remove(listeners[i]);
+ }
+ }
+ }
+ }
+
+ private static void clearDecorationScheduler() {
+ IWorkbench bench = PlatformUI.getWorkbench();
+ if (bench != null) {
+ IBaseLabelProvider bogusProvider = new BaseLabelProvider();
+
+ try {
+ // The DecoratorManager is a label-provider listener and
+ // it clears the scheduler on label-provider change events
+ ((ILabelProviderListener) bench.getDecoratorManager()).labelProviderChanged(new LabelProviderChangedEvent(bogusProvider));
+ } finally {
+ bogusProvider.dispose();
+ }
+ }
+ }
+
+ //
+ // Nested types
+ //
+
+ static class OperationHistoryHelper {
+
+ static final Field listenersField;
+
+ static final Set<Class<?>> historyListenerClasses;
+
+ static {
+ try {
+ listenersField = DefaultOperationHistory.class.getDeclaredField("listeners");
+ listenersField.setAccessible(true);
+
+ historyListenerClasses = Sets.<Class<?>> newHashSet( //
+ Platform.getBundle("org.eclipse.gmf.runtime.diagram.ui.actions").loadClass("org.eclipse.gmf.runtime.diagram.ui.actions.internal.PropertyChangeContributionItem"), //
+ Platform.getBundle("org.eclipse.ui.workbench").loadClass("org.eclipse.ui.operations.OperationHistoryActionHandler$HistoryListener"));
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ static ListenerList getOperationHistoryListeners() {
+ try {
+ return (ListenerList) listenersField.get(PlatformUI.getWorkbench().getOperationSupport().getOperationHistory());
+ } catch (Exception e) {
+ org.junit.Assert.fail(e.getLocalizedMessage());
+ return null; // Unreachable
+ }
+ }
+
+ static boolean shouldRemoveHistoryListener(Object listener) {
+ boolean result = historyListenerClasses.contains(listener.getClass().getName());
+
+ if (!result) {
+ // Maybe it's a subclass
+ for (Class<?> next : historyListenerClasses) {
+ if (next.isInstance(listener)) {
+ // Remember this
+ historyListenerClasses.add(listener.getClass());
+ result = true;
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Protocol for a delegate that provides the default test framework behaviour
+ * for the classification runner. These methods are as specified by the
+ * corresponding APIs of the {@link BlockJUnit4ClassRunner} class.
+ */
+ interface Delegate {
+ void runChild(FrameworkMethod method, RunNotifier notifier);
+
+ Description describeChild(FrameworkMethod method);
+
+ Object createTest() throws Exception;
+
+ List<TestRule> getTestRules(Object target);
+
+ Statement classBlock(RunNotifier notifier);
+ }
+}

Back to the top