From 18f83d6015edfd41b7ea579738ec2470600ff8bd Mon Sep 17 00:00:00 2001 From: Radoslav Husar Date: Tue, 19 May 2026 13:35:48 +0200 Subject: [PATCH 1/2] [#771] Fix @RepeatedTest unit test to simulate container mode Signed-off-by: Radoslav Husar --- .../JUnitJupiterRepeatedTestCase.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java b/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java index 7715923d8..0edfd95a2 100644 --- a/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java +++ b/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java @@ -21,11 +21,14 @@ import java.lang.reflect.Method; +import org.jboss.arquillian.junit5.extension.RunModeEvent; import org.jboss.arquillian.test.spi.LifecycleMethodExecutor; import org.jboss.arquillian.test.spi.TestMethodExecutor; import org.jboss.arquillian.test.spi.TestResult; import org.jboss.arquillian.test.spi.TestRunnerAdaptor; +import org.jboss.arquillian.test.spi.event.suite.TestLifecycleEvent; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -44,8 +47,16 @@ protected void executeAllLifeCycles(TestRunnerAdaptor adaptor) throws Exception doAnswer(new ExecuteLifecycle()).when(adaptor).before(any(Object.class), any(Method.class), any(LifecycleMethodExecutor.class)); doAnswer(new ExecuteLifecycle()).when(adaptor).after(any(Object.class), any(Method.class), any(LifecycleMethodExecutor.class)); doAnswer(new TestExecuteLifecycle()).when(adaptor).test(any(TestMethodExecutor.class)); + doAnswer(invocation -> { + TestLifecycleEvent event = invocation.getArgument(0); + if (event instanceof RunModeEvent) { + ((RunModeEvent) event).setRunAsClient(false); + } + return null; + }).when(adaptor).fireCustomLifecycle(any(TestLifecycleEvent.class)); } + @Disabled("https://github.com/arquillian/arquillian-core/issues/771") @Test public void shouldExecuteRepeatedTestExactlyThreeTimes() throws Exception { // given @@ -55,12 +66,13 @@ public void shouldExecuteRepeatedTestExactlyThreeTimes() throws Exception { // when TestExecutionSummary result = run(adaptor, ClassWithArquillianExtensionAndRepeatedTest.class); - // then — @RepeatedTest(3) must run exactly 3 times, not 9 - Assertions.assertEquals(3, result.getTestsSucceededCount()); + // then — @RepeatedTest(3) must invoke SPI lifecycle exactly 3 times, not 9 Assertions.assertEquals(0, result.getTestsFailedCount()); - Assertions.assertEquals(0, result.getTestsSkippedCount()); - assertCycle(1, Cycle.BEFORE_CLASS, Cycle.AFTER_CLASS); - assertCycle(3, Cycle.BEFORE, Cycle.TEST, Cycle.AFTER); + verify(adaptor, times(1)).beforeClass(any(Class.class), any(LifecycleMethodExecutor.class)); + verify(adaptor, times(1)).afterClass(any(Class.class), any(LifecycleMethodExecutor.class)); + verify(adaptor, times(3)).before(any(Object.class), any(Method.class), any(LifecycleMethodExecutor.class)); + verify(adaptor, times(3)).after(any(Object.class), any(Method.class), any(LifecycleMethodExecutor.class)); + verify(adaptor, times(1)).test(any(TestMethodExecutor.class)); } public static class TestExecuteLifecycle extends ExecuteLifecycle { From 8056adde3268aef8a33b4d096e8a0a11b23460e9 Mon Sep 17 00:00:00 2001 From: Radoslav Husar Date: Wed, 20 May 2026 10:29:24 +0200 Subject: [PATCH 2/2] [#771] Fix @RepeatedTest running N*N times in container mode ContextStore.getTemplateStore() used the per-invocation ExtensionContext, so each repetition had its own template registry and isRegisteredTemplate() never saw prior registrations. Use the parent context store which is shared across repetitions. Store the TestResult from the first invocation and replay it for subsequent repetitions so that failures are correctly reported for all repetitions. Also skip the JUnit5 invocation for already-registered templates and enable the integration test. Signed-off-by: Radoslav Husar --- .../test/lifecycle/RepeatedTestTest.java | 2 -- .../JUnitJupiterRepeatedTestCase.java | 18 ++++++++++++++++-- .../arquillian/junit5/ArquillianExtension.java | 4 ++++ .../jboss/arquillian/junit5/ContextStore.java | 18 +++++++++++------- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/integration-tests/junit5-tests/src/test/java/org/jboss/arquillian/integration/test/lifecycle/RepeatedTestTest.java b/integration-tests/junit5-tests/src/test/java/org/jboss/arquillian/integration/test/lifecycle/RepeatedTestTest.java index ae6b78c2b..dcc669e72 100644 --- a/integration-tests/junit5-tests/src/test/java/org/jboss/arquillian/integration/test/lifecycle/RepeatedTestTest.java +++ b/integration-tests/junit5-tests/src/test/java/org/jboss/arquillian/integration/test/lifecycle/RepeatedTestTest.java @@ -21,7 +21,6 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,7 +30,6 @@ import static org.jboss.arquillian.integration.test.lifecycle.FileWriterExtension.getTmpFilePath; import static org.jboss.arquillian.integration.test.lifecycle.FileWriterExtension.RunsWhere.SERVER; -@Disabled("https://github.com/arquillian/arquillian-core/issues/771") @ExtendWith(FileWriterExtension.class) @ArquillianTest @ExpectedTrace("repeated_test,repeated_test,repeated_test") diff --git a/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java b/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java index 0edfd95a2..3710d2a0f 100644 --- a/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java +++ b/junit5/container/src/test/java/org/jboss/arquillian/junit5/container/JUnitJupiterRepeatedTestCase.java @@ -28,7 +28,6 @@ import org.jboss.arquillian.test.spi.TestRunnerAdaptor; import org.jboss.arquillian.test.spi.event.suite.TestLifecycleEvent; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -56,7 +55,6 @@ protected void executeAllLifeCycles(TestRunnerAdaptor adaptor) throws Exception }).when(adaptor).fireCustomLifecycle(any(TestLifecycleEvent.class)); } - @Disabled("https://github.com/arquillian/arquillian-core/issues/771") @Test public void shouldExecuteRepeatedTestExactlyThreeTimes() throws Exception { // given @@ -75,6 +73,22 @@ public void shouldExecuteRepeatedTestExactlyThreeTimes() throws Exception { verify(adaptor, times(1)).test(any(TestMethodExecutor.class)); } + @Test + public void shouldReportFailuresForAllRepetitions() throws Exception { + // given + TestRunnerAdaptor adaptor = mock(TestRunnerAdaptor.class); + executeAllLifeCycles(adaptor); + doAnswer(invocation -> TestResult.failed(new AssertionError("expected failure"))) + .when(adaptor).test(any(TestMethodExecutor.class)); + + // when + TestExecutionSummary result = run(adaptor, ClassWithArquillianExtensionAndRepeatedTest.class); + + // then — all 3 repetitions must report as failed + Assertions.assertEquals(3, result.getTestsFailedCount()); + verify(adaptor, times(1)).test(any(TestMethodExecutor.class)); + } + public static class TestExecuteLifecycle extends ExecuteLifecycle { @Override diff --git a/junit5/core/src/main/java/org/jboss/arquillian/junit5/ArquillianExtension.java b/junit5/core/src/main/java/org/jboss/arquillian/junit5/ArquillianExtension.java index a9fd0b36e..daaf918ba 100644 --- a/junit5/core/src/main/java/org/jboss/arquillian/junit5/ArquillianExtension.java +++ b/junit5/core/src/main/java/org/jboss/arquillian/junit5/ArquillianExtension.java @@ -155,6 +155,10 @@ public void interceptTestTemplateMethod(Invocation invocation, ReflectiveI // Run as container (but only once) if (!contextStore.isRegisteredTemplate(invocationContext.getExecutable())) { result = interceptInvocation(invocation, extensionContext); + contextStore.registerTemplateResult(invocationContext.getExecutable(), result); + } else { + invocation.skip(); + result = contextStore.getRegisteredTemplateResult(invocationContext.getExecutable()); } } throwError(result); diff --git a/junit5/core/src/main/java/org/jboss/arquillian/junit5/ContextStore.java b/junit5/core/src/main/java/org/jboss/arquillian/junit5/ContextStore.java index b0c5363bd..940babab7 100644 --- a/junit5/core/src/main/java/org/jboss/arquillian/junit5/ContextStore.java +++ b/junit5/core/src/main/java/org/jboss/arquillian/junit5/ContextStore.java @@ -2,6 +2,7 @@ import java.lang.reflect.Method; +import org.jboss.arquillian.test.spi.TestResult; import org.junit.jupiter.api.extension.ExtensionContext; class ContextStore { @@ -26,17 +27,20 @@ ExtensionContext.Store getRootStore() { } private ExtensionContext.Store getTemplateStore() { - return context.getStore(ExtensionContext.Namespace.create(NAMESPACE_KEY, INTERCEPTED_TEMPLATE_NAMESPACE_KEY)); + ExtensionContext templateContext = context.getParent().orElse(context); + return templateContext.getStore(ExtensionContext.Namespace.create(NAMESPACE_KEY, INTERCEPTED_TEMPLATE_NAMESPACE_KEY)); } boolean isRegisteredTemplate(Method method) { - final ExtensionContext.Store templateStore = getTemplateStore(); + return getTemplateStore().get(method.toGenericString()) != null; + } + + void registerTemplateResult(Method method, TestResult result) { + getTemplateStore().put(method.toGenericString(), result != null ? result : TestResult.passed()); + } - final boolean isRegistered = templateStore.getOrDefault(method.toGenericString(), boolean.class, false); - if (!isRegistered) { - templateStore.put(method.toGenericString(), true); - } - return isRegistered; + TestResult getRegisteredTemplateResult(Method method) { + return getTemplateStore().get(method.toGenericString(), TestResult.class); } /**