Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2016-07-19 14:35:41 +0000
committerChristian W. Damus2016-07-19 15:16:16 +0000
commit1715bb61f46aa6251ff5bda74115e8e0530f5856 (patch)
treea8a06e1a00a199f90a97f0ae63e6e8bee58a1404
parent68f29daa4d3427f79f12524790fd71f8431486bc (diff)
downloadorg.eclipse.papyrus-1715bb61f46aa6251ff5bda74115e8e0530f5856.tar.gz
org.eclipse.papyrus-1715bb61f46aa6251ff5bda74115e8e0530f5856.tar.xz
org.eclipse.papyrus-1715bb61f46aa6251ff5bda74115e8e0530f5856.zip
Bug 498140: Memory leak in validation privileged runnables
https://bugs.eclipse.org/bugs/show_bug.cgi?id=498140 Forget the privileged runnable and its transaction context after executing the wrapping runnable-with-progress. Clarify the one-shot nature of the API. Similar changes for the headless progress runnable/callable APIs in the core. Change-Id: I5ab1361032ea5c48e493b86823e46413f1d199d3 (cherry picked from commit 03dd4b0b4844e55d22563ba72eb8eaabb7a9ca49)
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java85
-rw-r--r--plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelper.java81
-rw-r--r--tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java6
-rw-r--r--tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java194
-rw-r--r--tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/tests/AllTests.java4
-rw-r--r--tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelperTest.java127
6 files changed, 457 insertions, 40 deletions
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java
index 7f00384381a..90315f531b5 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others.
+ * 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
@@ -9,7 +9,7 @@
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
* Christian W. Damus (CEA) - bugs 429826, 408491, 433320
- * Christian W. Damus - bugs 451557, 457560, 461629, 463564, 466997, 465416, 485220
+ * Christian W. Damus - bugs 451557, 457560, 461629, 463564, 466997, 465416, 485220, 498140
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.utils;
@@ -19,6 +19,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.edit.domain.EditingDomain;
@@ -473,9 +474,16 @@ public class TransactionHelper {
}
/**
+ * <p>
* Create a privileged progress runnable, which is like a regular {@linkplain TransactionalEditingDomain#createPrivilegedRunnable(Runnable)
* privileged runnable} except that it is given a progress monitor for progress reporting.
- *
+ * </p>
+ * <b>Note</b> that a privileged progress-runnable can be used only once. Because it has the
+ * context of a borrowed transaction, repeated execution is invalid because the required
+ * transaction context will have expired. Attempting to run a privileged runnable a second
+ * time will throw an {@link IllegalStateException}.
+ * </p>
+ *
* @param domain
* an editing domain
* @param runnable
@@ -488,16 +496,21 @@ public class TransactionHelper {
Runnable privileged = domain.createPrivilegedRunnable(() -> runnable.run(monitorHolder[0]));
- return monitor -> {
- monitorHolder[0] = monitor;
- privileged.run();
- };
+ return new PrivilegedProgressRunnable(privileged, m -> monitorHolder[0] = m);
}
/**
+ * <p>
* Create a privileged progress callable, which is like a {@linkplain TransactionalEditingDomain#createPrivilegedRunnable(Runnable)
* privileged runnable} except that it is given a progress monitor for progress reporting and it computes a result.
- *
+ * </p>
+ * <p>
+ * <b>Note</b> that a privileged progress-callable can be used only once. Because it has the
+ * context of a borrowed transaction, repeated execution is invalid because the required
+ * transaction context will have expired. Attempting to run a privileged callable a second
+ * time will throw an {@link IllegalStateException}.
+ * </p>
+ *
* @param callable
* an editing domain
* @param callable
@@ -506,22 +519,27 @@ public class TransactionHelper {
* @since 2.0
*/
public static <V> IProgressCallable<V> createPrivilegedCallable(TransactionalEditingDomain domain, final IProgressCallable<V> callable) {
- IProgressMonitor monitorHolder[] = { null };
- AtomicReference<V> resultHolder = new AtomicReference<V>();
+ AtomicReference<V> resultHolder = new AtomicReference<>();
Exception failHolder[] = { null };
- Runnable privileged = domain.createPrivilegedRunnable(() -> {
+ IProgressRunnable[] privileged = { createPrivilegedRunnable(domain, monitor -> {
try {
- resultHolder.set(callable.call(monitorHolder[0]));
+ resultHolder.set(callable.call(monitor));
} catch (Exception e) {
failHolder[0] = e;
}
- });
+ }) };
return monitor -> {
- monitorHolder[0] = monitor;
+ if (privileged[0] == null) {
+ throw new IllegalStateException("Privileged callable was already run"); //$NON-NLS-1$
+ }
- privileged.run();
+ try {
+ privileged[0].run(monitor);
+ } finally {
+ privileged[0] = null;
+ }
if (failHolder[0] != null) {
throw failHolder[0];
@@ -530,4 +548,41 @@ public class TransactionHelper {
return resultHolder.get();
};
}
+
+ //
+ // Nested types
+ //
+
+ private static final class PrivilegedProgressRunnable implements IProgressRunnable {
+ private final Consumer<? super IProgressMonitor> monitorSlot;
+ private Runnable privileged;
+
+ PrivilegedProgressRunnable(Runnable privileged, Consumer<? super IProgressMonitor> monitorSlot) {
+ super();
+
+ this.monitorSlot = monitorSlot;
+ this.privileged = privileged;
+ }
+
+ @Override
+ public void run(IProgressMonitor monitor) {
+ if (privileged == null) {
+ throw new IllegalArgumentException("Privileged runnable was already run"); //$NON-NLS-1$
+ }
+
+ monitorSlot.accept(monitor);
+
+ try {
+ privileged.run();
+ } finally {
+ // Clear our reference to the runnable, which holds a reference
+ // to the transaction which, in turn, holds monitors. If we
+ // are run in the ModalContextThread and initialize an Xtext UI
+ // bundle, then that thread will be retained forever in its
+ // Guice injector as the creating thread, and that thread then
+ // will retain me (its runnable). See bug 498140
+ privileged = null;
+ }
+ }
+ }
}
diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelper.java b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelper.java
index 1fdc3344e41..6ee4ed2b14c 100644
--- a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelper.java
+++ b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelper.java
@@ -8,12 +8,13 @@
*
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
- * Christian W. Damus - bug 465416
+ * Christian W. Damus - bugs 465416, 498140
*
*****************************************************************************/
package org.eclipse.papyrus.infra.ui.util;
import java.lang.reflect.InvocationTargetException;
+import java.util.function.Consumer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
@@ -30,10 +31,17 @@ import org.eclipse.jface.operation.IRunnableWithProgress;
public class TransactionUIHelper {
/**
+ * <p>
* Create a privileged runnable with progress, which is like a regular {@linkplain TransactionalEditingDomain#createPrivilegedRunnable(Runnable)
* privileged runnable} except that it is given a progress monitor for progress reporting.
* This enables execution of monitored runnables on the modal-context thread using the transaction borrowed from the UI thread.
- *
+ * </p>
+ * <b>Note</b> that a privileged progress-runnable can be used only once. Because it has the
+ * context of a borrowed transaction, repeated execution is invalid because the required
+ * transaction context will have expired. Attempting to run a privileged runnable a second
+ * time will throw an {@link IllegalStateException}.
+ * </p>
+ *
* @param domain
* an editing domain
* @param runnable
@@ -43,7 +51,7 @@ public class TransactionUIHelper {
public static IRunnableWithProgress createPrivilegedRunnableWithProgress(TransactionalEditingDomain domain, final IRunnableWithProgress runnable) {
final IProgressMonitor monitorHolder[] = { null };
- final Runnable privileged = domain.createPrivilegedRunnable(new Runnable() {
+ Runnable privileged = domain.createPrivilegedRunnable(new Runnable() {
@Override
public void run() {
@@ -53,31 +61,60 @@ public class TransactionUIHelper {
throw e;
} catch (Exception e) {
throw new WrappedException(e);
+ } finally {
+ monitorHolder[0] = null;
}
}
});
- return new IRunnableWithProgress() {
+ return new PrivilegedRunnableWithProgress(privileged, m -> monitorHolder[0] = m);
+ }
- @Override
- public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
- monitorHolder[0] = monitor;
+ //
+ // Nested types
+ //
- try {
- privileged.run();
- } catch (OperationCanceledException e) {
- throw new InterruptedException(e.getLocalizedMessage());
- } catch (WrappedException e) {
- Exception unwrapped = e.exception();
- if (unwrapped instanceof InvocationTargetException) {
- throw (InvocationTargetException) unwrapped;
- } else if (unwrapped instanceof InterruptedException) {
- throw (InterruptedException) unwrapped;
- } else {
- throw e;
- }
+ private static final class PrivilegedRunnableWithProgress implements IRunnableWithProgress {
+ private final Consumer<? super IProgressMonitor> monitorSlot;
+ private Runnable privileged;
+
+ PrivilegedRunnableWithProgress(Runnable privileged, Consumer<? super IProgressMonitor> monitorSlot) {
+ super();
+
+ this.monitorSlot = monitorSlot;
+ this.privileged = privileged;
+ }
+
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ if (privileged == null) {
+ throw new IllegalStateException("Privileged runnable was already run"); //$NON-NLS-1$
+ }
+
+ monitorSlot.accept(monitor);
+
+ try {
+ privileged.run();
+ } catch (OperationCanceledException e) {
+ throw new InterruptedException(e.getLocalizedMessage());
+ } catch (WrappedException e) {
+ Exception unwrapped = e.exception();
+ if (unwrapped instanceof InvocationTargetException) {
+ throw (InvocationTargetException) unwrapped;
+ } else if (unwrapped instanceof InterruptedException) {
+ throw (InterruptedException) unwrapped;
+ } else {
+ throw e;
}
+ } finally {
+ // Clear our reference to the runnable, which holds a reference
+ // to the transaction which, in turn, holds monitors. If we
+ // are run in the ModalContextThread and initialize an Xtext UI
+ // bundle, then that thread will be retained forever in its
+ // Guice injector as the creating thread, and that thread then
+ // will retain me (its runnable). See bug 498140
+ privileged = null;
}
- };
- }
+ }
+ };
}
diff --git a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java
index 063762cff96..9c4476c3738 100644
--- a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java
+++ b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java
@@ -10,7 +10,7 @@
* Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - Initial API and implementation
* Christian W. Damus (CEA LIST) - add test for AdapterUtils
* Christian W. Damus (CEA) - bugs 402525, 422257, 399859
- * Christian W. Damus - bugs 456934, 468030, 482949, 485220, 488558
+ * Christian W. Damus - bugs 456934, 468030, 482949, 485220, 488558, 498140
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.tests;
@@ -27,6 +27,7 @@ import org.eclipse.papyrus.infra.core.services.SharedServiceFactoryTest;
import org.eclipse.papyrus.infra.core.utils.AdapterUtilsTest;
import org.eclipse.papyrus.infra.core.utils.JobBasedFutureTest;
import org.eclipse.papyrus.infra.core.utils.JobExecutorServiceTest;
+import org.eclipse.papyrus.infra.core.utils.TransactionHelperTest;
import org.eclipse.papyrus.junit.framework.classification.ClassificationSuite;
import org.eclipse.papyrus.junit.framework.runner.Headless;
import org.junit.runner.RunWith;
@@ -45,7 +46,8 @@ import org.junit.runners.Suite.SuiteClasses;
// {oep}.core.services
ComposedServiceTest.class, ServicesRegistryTest.class, SharedServiceFactoryTest.class,
// {oep}.core.utils
- AdapterUtilsTest.class, JobBasedFutureTest.class, JobExecutorServiceTest.class
+ AdapterUtilsTest.class, JobBasedFutureTest.class, JobExecutorServiceTest.class,
+ TransactionHelperTest.class,
})
/**
* Suite Class for all tests in the plugin
diff --git a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java
new file mode 100644
index 00000000000..6499866a806
--- /dev/null
+++ b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java
@@ -0,0 +1,194 @@
+/*****************************************************************************
+ * Copyright (c) 2016 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.core.utils;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EcoreFactory;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.transaction.RecordingCommand;
+import org.eclipse.emf.transaction.TransactionalEditingDomain;
+import org.eclipse.papyrus.infra.tools.util.IProgressCallable;
+import org.eclipse.papyrus.infra.tools.util.IProgressRunnable;
+import org.eclipse.papyrus.junit.utils.PrintingProgressMonitor;
+import org.eclipse.papyrus.junit.utils.rules.HouseKeeper;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Automated tests for the {@link TransactionHelper} class.
+ */
+public class TransactionHelperTest {
+
+ private static ExecutorService exec;
+
+ @Rule
+ public final HouseKeeper houseKeeper = new HouseKeeper();
+
+ private TransactionalEditingDomain domain;
+ private EClass eclass;
+
+ /**
+ * Initializes me.
+ */
+ public TransactionHelperTest() {
+ super();
+ }
+
+ @Test
+ public void testPrivilegedProgressRunnable() {
+ IProgressRunnable runnable = monitor -> {
+ monitor.beginTask("Test privileged runnable", 2);
+
+ eclass.setAbstract(true);
+ monitor.worked(1);
+ eclass.setName("Foo");
+ monitor.worked(1);
+
+ monitor.done();
+ };
+
+ FailureAssertion failure = new FailureAssertion();
+ domain.getCommandStack().execute(new RecordingCommand(domain, "Test") {
+
+ @Override
+ protected void doExecute() {
+ IProgressRunnable privileged = TransactionHelper.createPrivilegedRunnable(domain, runnable);
+ Future<?> future = submit(privileged);
+
+ // And synchronize with it
+ try {
+ future.get();
+ } catch (Exception e) {
+ failure.accept(e);
+ }
+ }
+ });
+
+ failure.verify();
+
+ assertThat(eclass.isAbstract(), is(true));
+ assertThat(eclass.getName(), is("Foo"));
+ }
+
+ @Test
+ public void testPrivilegedProgressCallable() {
+ IProgressCallable<String> callable = monitor -> {
+ monitor.beginTask("Test privileged callable", 2);
+
+ eclass.setAbstract(true);
+ monitor.worked(1);
+ eclass.setName("Foo");
+ monitor.worked(1);
+
+ monitor.done();
+
+ return eclass.getName();
+ };
+
+ FailureAssertion failure = new FailureAssertion();
+ domain.getCommandStack().execute(new RecordingCommand(domain, "Test") {
+
+ @Override
+ protected void doExecute() {
+ IProgressCallable<String> privileged = TransactionHelper.createPrivilegedCallable(domain, callable);
+ Future<String> future = submit(privileged);
+
+ // And synchronize with it
+ try {
+ String newName = future.get();
+ assertThat(newName, is("Foo"));
+ } catch (Exception e) {
+ failure.accept(e);
+ }
+ }
+ });
+
+ failure.verify();
+
+ assertThat(eclass.isAbstract(), is(true));
+ assertThat(eclass.getName(), is("Foo"));
+ }
+
+ //
+ // Test framework
+ //
+
+ @BeforeClass
+ public static void createExecutorService() {
+ exec = Executors.newSingleThreadExecutor();
+ }
+
+ @Before
+ public void createFixture() throws Exception {
+ domain = houseKeeper.createSimpleEditingDomain();
+ Resource res = domain.createResource("file:bogus.ecore");
+
+ TransactionHelper.run(domain, () -> {
+ EPackage epackage = EcoreFactory.eINSTANCE.createEPackage();
+ res.getContents().add(epackage);
+ eclass = EcoreFactory.eINSTANCE.createEClass();
+ epackage.getEClassifiers().add(eclass);
+ });
+ }
+
+ @AfterClass
+ public static void shutdownExecutorService() {
+ exec.shutdown();
+ exec = null;
+ }
+
+ <V> Future<V> submit(IProgressCallable<V> callable) {
+ return exec.submit(() -> callable.call(new PrintingProgressMonitor()));
+ }
+
+ Future<?> submit(IProgressRunnable runnable) {
+ return exec.submit(() -> runnable.run(new PrintingProgressMonitor()));
+ }
+
+ //
+ // Nested types
+ //
+
+ private static final class FailureAssertion implements Consumer<Throwable> {
+ private Throwable thrown;
+
+ @Override
+ public void accept(Throwable t) {
+ // Take only the first one
+ if (thrown == null) {
+ thrown = t;
+ }
+ }
+
+ void verify() {
+ if (thrown != null) {
+ thrown.printStackTrace();
+ fail("Synchronization on future result failed: " + thrown.getMessage());
+ }
+ }
+ }
+}
diff --git a/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/tests/AllTests.java b/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/tests/AllTests.java
index 5d201a4bae4..3fd710a50e9 100644
--- a/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/tests/AllTests.java
+++ b/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/tests/AllTests.java
@@ -8,7 +8,7 @@
*
* Contributors:
* Christian W. Damus (CEA) - Initial API and implementation
- * Christian W. Damus - bug 485220
+ * Christian W. Damus - bugs 485220, 498140
*
*/
package org.eclipse.papyrus.infra.ui.tests;
@@ -18,6 +18,7 @@ import org.eclipse.papyrus.infra.ui.lifecycleevents.LifeCycleEventsProviderTest;
import org.eclipse.papyrus.infra.ui.lifecycleevents.SaveAndDirtyServiceTest;
import org.eclipse.papyrus.infra.ui.providers.DelegatingPapyrusContentProviderTest;
import org.eclipse.papyrus.infra.ui.providers.SemanticContentProviderFactoryTest;
+import org.eclipse.papyrus.infra.ui.util.TransactionUIHelperTest;
import org.eclipse.papyrus.infra.ui.util.UIUtilTest;
import org.eclipse.papyrus.junit.framework.classification.ClassificationSuite;
import org.junit.runner.RunWith;
@@ -29,6 +30,7 @@ import org.junit.runners.Suite.SuiteClasses;
*/
@RunWith(ClassificationSuite.class)
@SuiteClasses({ UIUtilTest.class,
+ TransactionUIHelperTest.class,
SaveAndDirtyServiceTest.class,
LifeCycleEventsProviderTest.class,
NestedEditorDelegatedOutlinePageTest.class,
diff --git a/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelperTest.java b/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelperTest.java
new file mode 100644
index 00000000000..fb0b509d8a8
--- /dev/null
+++ b/tests/junit/plugins/infra/org.eclipse.papyrus.infra.ui.tests/src/org/eclipse/papyrus/infra/ui/util/TransactionUIHelperTest.java
@@ -0,0 +1,127 @@
+/*****************************************************************************
+ * Copyright (c) 2016 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.ui.util;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.function.Consumer;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EcoreFactory;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.transaction.RecordingCommand;
+import org.eclipse.emf.transaction.TransactionalEditingDomain;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.papyrus.infra.core.utils.TransactionHelper;
+import org.eclipse.papyrus.junit.utils.rules.HouseKeeper;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Automated tests for the {@link TransactionUIHelper} class.
+ */
+public class TransactionUIHelperTest {
+
+ @Rule
+ public final HouseKeeper houseKeeper = new HouseKeeper();
+
+ private TransactionalEditingDomain domain;
+ private EClass eclass;
+
+ /**
+ * Initializes me.
+ */
+ public TransactionUIHelperTest() {
+ super();
+ }
+
+ @Test
+ public void testPrivilegedRunnableWithProgress() {
+ IRunnableWithProgress runnable = monitor -> {
+ monitor.beginTask("Test privileged runnable", 2);
+
+ eclass.setAbstract(true);
+ monitor.worked(1);
+ eclass.setName("Foo");
+ monitor.worked(1);
+
+ monitor.done();
+ };
+
+ FailureAssertion failure = new FailureAssertion();
+ domain.getCommandStack().execute(new RecordingCommand(domain, "Test") {
+
+ @Override
+ protected void doExecute() {
+ IRunnableWithProgress privileged = TransactionUIHelper.createPrivilegedRunnableWithProgress(domain, runnable);
+
+ try {
+ new ProgressMonitorDialog(null).run(true, true, privileged);
+ } catch (Exception e) {
+ failure.accept(e);
+ }
+ }
+ });
+
+ failure.verify();
+
+ assertThat(eclass.isAbstract(), is(true));
+ assertThat(eclass.getName(), is("Foo"));
+ }
+
+ //
+ // Test framework
+ //
+
+ @Before
+ public void createFixture() throws Exception {
+ domain = houseKeeper.createSimpleEditingDomain();
+ Resource res = domain.createResource("file:bogus.ecore");
+
+ TransactionHelper.run(domain, () -> {
+ EPackage epackage = EcoreFactory.eINSTANCE.createEPackage();
+ res.getContents().add(epackage);
+ eclass = EcoreFactory.eINSTANCE.createEClass();
+ epackage.getEClassifiers().add(eclass);
+ });
+ }
+
+ //
+ // Nested types
+ //
+
+ private static final class FailureAssertion implements Consumer<Throwable> {
+ private Throwable thrown;
+
+ @Override
+ public void accept(Throwable t) {
+ // Take only the first one
+ if (thrown == null) {
+ thrown = t;
+ }
+ }
+
+ void verify() {
+ if (thrown != null) {
+ thrown.printStackTrace();
+ fail("Synchronization on future result failed: " + thrown.getMessage());
+ }
+ }
+ }
+}

Back to the top