diff --git a/org.eclipse.scout.rt.client.test/src/main/java/org/eclipse/scout/rt/testing/client/runner/statement/TimeoutClientRunContextStatement.java b/org.eclipse.scout.rt.client.test/src/main/java/org/eclipse/scout/rt/testing/client/runner/statement/TimeoutClientRunContextStatement.java index 739dd0e5114..d0d64e53a21 100644 --- a/org.eclipse.scout.rt.client.test/src/main/java/org/eclipse/scout/rt/testing/client/runner/statement/TimeoutClientRunContextStatement.java +++ b/org.eclipse.scout.rt.client.test/src/main/java/org/eclipse/scout/rt/testing/client/runner/statement/TimeoutClientRunContextStatement.java @@ -1,27 +1,22 @@ /* - * Copyright (c) 2010-2017 BSI Business Systems Integration AG. + * Copyright (c) 2010-2023 BSI Business Systems Integration AG. * 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 + * https://www.eclipse.org/legal/epl-v10.html * * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ package org.eclipse.scout.rt.testing.client.runner.statement; -import java.util.concurrent.TimeUnit; - import org.eclipse.scout.rt.client.context.ClientRunContexts; import org.eclipse.scout.rt.client.job.ModelJobs; import org.eclipse.scout.rt.platform.job.IFuture; -import org.eclipse.scout.rt.platform.util.Assertions; -import org.eclipse.scout.rt.platform.util.concurrent.ThreadInterruptedError; -import org.eclipse.scout.rt.platform.util.concurrent.TimedOutError; -import org.eclipse.scout.rt.testing.platform.runner.SafeStatementInvoker; +import org.eclipse.scout.rt.platform.util.concurrent.IRunnable; +import org.eclipse.scout.rt.testing.platform.runner.statement.AbstractTimeoutRunContextStatement; import org.junit.Test; import org.junit.runners.model.Statement; -import org.junit.runners.model.TestTimedOutException; /** * Statement for executing tests with a timeout (i.e. the annotated test method is expected to complete within the @@ -31,35 +26,14 @@ * @see Test#timeout() * @since 5.1 */ -public class TimeoutClientRunContextStatement extends Statement { - - protected final Statement m_next; - private final long m_timeoutMillis; +public class TimeoutClientRunContextStatement extends AbstractTimeoutRunContextStatement { public TimeoutClientRunContextStatement(final Statement next, final long timeoutMillis) { - m_timeoutMillis = timeoutMillis; - m_next = Assertions.assertNotNull(next, "next statement must not be null"); + super(next, timeoutMillis); } @Override - public void evaluate() throws Throwable { - final SafeStatementInvoker invoker = new SafeStatementInvoker(m_next); - - final IFuture future = ModelJobs.schedule(invoker, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("Running test with support for JUnit timeout")); - - try { - if (m_timeoutMillis <= 0) { - future.awaitDone(); - } - else { - future.awaitDone(m_timeoutMillis, TimeUnit.MILLISECONDS); - } - } - catch (ThreadInterruptedError | TimedOutError e) { // NOSONAR - future.cancel(true); - throw new TestTimedOutException(m_timeoutMillis, TimeUnit.MILLISECONDS); // JUnit timeout exception - } - - invoker.throwOnError(); + protected IFuture createFuture(IRunnable runnable) { + return ModelJobs.schedule(runnable, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("Running test with support for JUnit timeout")); } } diff --git a/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/AbstractTimeoutRunContextStatement.java b/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/AbstractTimeoutRunContextStatement.java new file mode 100644 index 00000000000..fa59558e6c8 --- /dev/null +++ b/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/AbstractTimeoutRunContextStatement.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010-2023 BSI Business Systems Integration AG. + * 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + */ +package org.eclipse.scout.rt.testing.platform.runner.statement; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.scout.rt.platform.job.IFuture; +import org.eclipse.scout.rt.platform.util.Assertions; +import org.eclipse.scout.rt.platform.util.concurrent.IRunnable; +import org.eclipse.scout.rt.platform.util.concurrent.ThreadInterruptedError; +import org.eclipse.scout.rt.platform.util.concurrent.TimedOutError; +import org.eclipse.scout.rt.testing.platform.runner.SafeStatementInvoker; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestTimedOutException; + +public abstract class AbstractTimeoutRunContextStatement extends Statement { + + protected final Statement m_next; + protected final long m_timeoutMillis; + + public AbstractTimeoutRunContextStatement(final Statement next, final long timeoutMillis) { + m_next = Assertions.assertNotNull(next, "next statement must not be null"); + m_timeoutMillis = timeoutMillis; + } + + protected abstract IFuture createFuture(IRunnable runnable); + + @Override + public void evaluate() throws Throwable { + final SafeStatementInvoker invoker = new SafeStatementInvoker(m_next); + class TimeoutFutureRunnable implements IRunnable { + + protected Thread m_thread; + + @Override + public void run() throws Exception { + try { + m_thread = Thread.currentThread(); + invoker.run(); + } + finally { + m_thread = null; + } + } + + public StackTraceElement[] getStackTrace() { + Thread t = m_thread; + if (t != null) { + return t.getStackTrace(); + } + return new StackTraceElement[0]; + } + } + final TimeoutFutureRunnable runnable = new TimeoutFutureRunnable(); + + final IFuture future = createFuture(runnable); + try { + if (m_timeoutMillis <= 0) { + future.awaitDone(); + } + else { + future.awaitDone(m_timeoutMillis, TimeUnit.MILLISECONDS); + } + } + catch (ThreadInterruptedError | TimedOutError e) { // NOSONAR + System.err.println("Test has timed out, cancelling future (if test threw an exception, original exception is still thrown)"); + StackTraceElement[] stackTrace = runnable.getStackTrace(); + future.cancel(true); + printStackTrace(stackTrace); + invoker.throwOnError(); // throw if test itself has thrown an error + throw new TestTimedOutException(m_timeoutMillis, TimeUnit.MILLISECONDS); // JUnit timeout exception + } + + invoker.throwOnError(); + } + + protected void printStackTrace(StackTraceElement[] stackTrace) { + StringBuilder trace = new StringBuilder(); + for (StackTraceElement traceElement : stackTrace) { + if (trace.length() != 0) { + trace.append('\n'); + } + trace.append("\tat "); + trace.append(traceElement); + } + if (trace.length() > 0) { + System.err.println("Test timed out\n" + trace); + } + } +} diff --git a/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatement.java b/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatement.java index 95c69378124..6d016c866ba 100644 --- a/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatement.java +++ b/org.eclipse.scout.rt.platform.test/src/main/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatement.java @@ -1,29 +1,25 @@ /* - * Copyright (c) 2010-2017 BSI Business Systems Integration AG. + * Copyright (c) 2010-2023 BSI Business Systems Integration AG. * 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 + * https://www.eclipse.org/legal/epl-v10.html * * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ package org.eclipse.scout.rt.testing.platform.runner.statement; -import java.util.concurrent.TimeUnit; - +import org.eclipse.scout.rt.platform.BEANS; import org.eclipse.scout.rt.platform.context.RunContext; +import org.eclipse.scout.rt.platform.context.RunMonitor; import org.eclipse.scout.rt.platform.job.IFuture; import org.eclipse.scout.rt.platform.job.Jobs; import org.eclipse.scout.rt.platform.transaction.TransactionScope; -import org.eclipse.scout.rt.platform.util.Assertions; -import org.eclipse.scout.rt.platform.util.concurrent.ThreadInterruptedError; -import org.eclipse.scout.rt.platform.util.concurrent.TimedOutError; -import org.eclipse.scout.rt.testing.platform.runner.SafeStatementInvoker; +import org.eclipse.scout.rt.platform.util.concurrent.IRunnable; import org.junit.Test; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runners.model.Statement; -import org.junit.runners.model.TestTimedOutException; /** * JUnit runs tests with a timeout in a separate thread. This statement is a replace for {@link FailOnTimeout}, and @@ -36,31 +32,20 @@ * @see FailOnTimeout * @since 5.1 */ -public class TimeoutRunContextStatement extends Statement { - - private final Statement m_next; - private final long m_timeoutMillis; +public class TimeoutRunContextStatement extends AbstractTimeoutRunContextStatement { public TimeoutRunContextStatement(final Statement next, final long timeoutMillis) { - m_next = Assertions.assertNotNull(next, "next statement must not be null"); - m_timeoutMillis = timeoutMillis; + super(next, timeoutMillis); } @Override - public void evaluate() throws Throwable { - final SafeStatementInvoker invoker = new SafeStatementInvoker(m_next); - - final IFuture future = Jobs.schedule(invoker, Jobs.newInput() - .withRunContext(RunContext.CURRENT.get().copy().withTransactionScope(TransactionScope.REQUIRES_NEW)) // Run in new TX, because the same TX is not allowed to be used by multiple threads. + protected IFuture createFuture(IRunnable runnable) { + RunMonitor newRunMonitor = BEANS.get(RunMonitor.class); + return Jobs.schedule(runnable, Jobs.newInput() + .withRunContext(RunContext.CURRENT.get().copy() + .withRunMonitor(newRunMonitor) + .withParentRunMonitor(RunMonitor.CURRENT.get()) + .withTransactionScope(TransactionScope.REQUIRES_NEW)) // Run in new TX, because the same TX is not allowed to be used by multiple threads. .withName("Running test with support for JUnit timeout")); - try { - future.awaitDone(m_timeoutMillis, TimeUnit.MILLISECONDS); - } - catch (ThreadInterruptedError | TimedOutError e) { // NOSONAR - future.cancel(true); - throw new TestTimedOutException(m_timeoutMillis, TimeUnit.MILLISECONDS); // JUnit timeout exception - } - - invoker.throwOnError(); } } diff --git a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatementTest.java b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatementTest.java new file mode 100644 index 00000000000..44fcfca84e8 --- /dev/null +++ b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/testing/platform/runner/statement/TimeoutRunContextStatementTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2023 BSI Business Systems Integration AG. + * 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + */ +package org.eclipse.scout.rt.testing.platform.runner.statement; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.scout.rt.platform.util.SleepUtil; +import org.eclipse.scout.rt.testing.platform.runner.PlatformTestRunner; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestTimedOutException; + +@RunWith(PlatformTestRunner.class) +public class TimeoutRunContextStatementTest { + + @Test + public void testTestTimedOutException() { + Assert.assertThrows(TestTimedOutException.class, () -> new TimeoutRunContextStatement(new Statement() { + @Override + public void evaluate() { + SleepUtil.sleepSafe(5L, TimeUnit.SECONDS); + } + }, TimeUnit.SECONDS.toMillis(1)).evaluate()); + } + + @Test + public void testOriginalExceptionIsThrownEvenOnTimeout() { + Assert.assertThrows(InterruptedException.class, () -> new TimeoutRunContextStatement(new Statement() { + @Override + public void evaluate() throws InterruptedException { + Thread.sleep(TimeUnit.SECONDS.toMillis(5)); + } + }, TimeUnit.SECONDS.toMillis(1)).evaluate()); + } +}