Skip to content

Commit 67c4171

Browse files
committed
add support for LifecycleMethodExecutionExceptionHandler (for spock's setupSpec, setup etc.)
1 parent 36e1a96 commit 67c4171

File tree

44 files changed

+1119
-50
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1119
-50
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
* Update junit compatibility
22
- [junit 5.11](https://docs.junit.org/5.11.0/release-notes/#release-notes-5.11.0-junit-jupiter-deprecations-and-breaking-changes):
3-
@ExtendWith on non-static field now participate in beforeAll/afterAll;
4-
static extension fields are registered before non-static fields (fixed order)
3+
* @ExtendWith on a non-static field now participate in beforeAll/afterAll;
4+
* static extension fields are registered before non-static fields (junit behavior change)
5+
* Add support for LifecycleMethodExecutionExceptionHandler (for spock's setupSpec, setup etc.)
56

67
### 1.2.0 (2022-11-25)
78
* Add root (engine-level) context to be able to use global storage in extensions (#44)

src/main/java/ru/vyarus/spock/jupiter/engine/ExtensionUtils.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public final class ExtensionUtils {
8181
ParameterResolver.class,
8282
TestInstancePostProcessor.class,
8383
TestInstancePreDestroyCallback.class,
84-
TestExecutionExceptionHandler.class
84+
TestExecutionExceptionHandler.class,
85+
LifecycleMethodExecutionExceptionHandler.class
8586
);
8687

8788
public static final List<Class<? extends Extension>> UNSUPPORTED_EXTENSIONS = Arrays.asList(
@@ -91,8 +92,6 @@ public final class ExtensionUtils {
9192
// impossible to add (spock does not allow this)
9293
TestInstanceFactory.class,
9394
TestInstancePreConstructCallback.class,
94-
// could be supported, but what for?
95-
LifecycleMethodExecutionExceptionHandler.class,
9695
InvocationInterceptor.class,
9796
// support could be added, but this is too specific (will never be required)
9897
TestWatcher.class

src/main/java/ru/vyarus/spock/jupiter/interceptor/ExtensionLifecycleMerger.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ public ExtensionLifecycleMerger(final ClassContext context) {
6262
ctx = getMethodContext(invocation);
6363
}
6464
injectArguments(invocation, ctx);
65-
invocation.proceed();
65+
// intercepting here because the main interceptor does not receive exceptions (they are already eaten)
66+
try {
67+
invocation.proceed();
68+
} catch (Throwable ex) {
69+
handleFixtureMethodException(invocation, ctx, ex);
70+
}
6671
};
6772
}
6873

@@ -214,4 +219,25 @@ private void injectArguments(final IMethodInvocation invocation, final AbstractC
214219
}
215220
}
216221
}
222+
223+
private void handleFixtureMethodException(final IMethodInvocation invocation,
224+
final AbstractContext context,
225+
final Throwable throwable) {
226+
switch (invocation.getMethod().getKind()) {
227+
case SETUP_SPEC:
228+
junit.handleSetupSpecMethodException(context, throwable);
229+
break;
230+
case SETUP:
231+
junit.handleSetupMethodException(context, throwable);
232+
break;
233+
case CLEANUP:
234+
junit.handleCleanupMethodException(context, throwable);
235+
break;
236+
case CLEANUP_SPEC:
237+
junit.handleCleanupSpecMethodException(context, throwable);
238+
break;
239+
default:
240+
throw new IllegalStateException("Unexpected method kind: " + invocation.getMethod().getKind());
241+
}
242+
}
217243
}

src/main/java/ru/vyarus/spock/jupiter/interceptor/JunitApiExecutor.java

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.junit.jupiter.api.extension.BeforeEachCallback;
88
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
99
import org.junit.jupiter.api.extension.Extension;
10+
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
1011
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
1112
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
1213
import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback;
@@ -16,6 +17,7 @@
1617
import org.junit.platform.commons.util.ExceptionUtils;
1718
import org.junit.platform.commons.util.UnrecoverableExceptions;
1819
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
20+
import ru.vyarus.spock.jupiter.engine.ExtensionRegistry;
1921
import ru.vyarus.spock.jupiter.engine.context.AbstractContext;
2022
import ru.vyarus.spock.jupiter.engine.context.ClassContext;
2123
import ru.vyarus.spock.jupiter.engine.context.MethodContext;
@@ -71,11 +73,11 @@ public void beforeAll(final ClassContext context) {
7173
// org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance
7274
public void instancePostProcessors(final ClassContext context, final Object instance) {
7375
context.getCollector().execute(() -> {
74-
getExtensions(context, TestInstancePostProcessor.class).forEach(
75-
extension -> executeAndMaskThrowable(() ->
76-
extension.postProcessTestInstance(instance, context)));
77-
// init non-static field extensions
78-
context.getRegistry().initializeExtensions(context.getRequiredTestClass(), instance);
76+
getExtensions(context, TestInstancePostProcessor.class).forEach(
77+
extension -> executeAndMaskThrowable(() ->
78+
extension.postProcessTestInstance(instance, context)));
79+
// init non-static field extensions
80+
context.getRegistry().initializeExtensions(context.getRequiredTestClass(), instance);
7981
});
8082
context.getCollector().assertEmpty();
8183

@@ -131,15 +133,43 @@ public void instancePreDestroy(final MethodContext context) {
131133

132134
// org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeAfterAllCallbacks
133135
public void afterAll(final ClassContext context) {
134-
final ThrowableCollector collector = context.getCollector();
135-
afterAllExtensions.forEach(
136-
extension -> collector.execute(() -> extension.afterAll(context)));
137-
collector.assertEmpty();
136+
// null should be not possible, but just in case
137+
if (afterAllExtensions != null) {
138+
final ThrowableCollector collector = context.getCollector();
139+
afterAllExtensions.forEach(
140+
extension -> collector.execute(() -> extension.afterAll(context)));
141+
collector.assertEmpty();
142+
}
143+
}
144+
145+
public void handleTestException(final MethodContext context, final Throwable throwable) {
146+
invokeExecutionExceptionHandlers(TestExecutionExceptionHandler.class, context.getRegistry(), throwable,
147+
(handler, handledThrowable) -> handler
148+
.handleTestExecutionException(context, handledThrowable));
149+
}
150+
151+
public void handleSetupSpecMethodException(final AbstractContext context, final Throwable throwable) {
152+
invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, context.getRegistry(),
153+
throwable, (handler, handledThrowable) -> handler
154+
.handleBeforeAllMethodExecutionException(context, handledThrowable));
155+
}
156+
157+
public void handleSetupMethodException(final AbstractContext context, final Throwable throwable) {
158+
invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, context.getRegistry(),
159+
throwable, (handler, handledThrowable) -> handler
160+
.handleBeforeEachMethodExecutionException(context, handledThrowable));
161+
}
162+
163+
public void handleCleanupMethodException(final AbstractContext context, final Throwable throwable) {
164+
invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, context.getRegistry(),
165+
throwable, (handler, handledThrowable) -> handler
166+
.handleAfterEachMethodExecutionException(context, handledThrowable));
138167
}
139168

140-
// org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestExecutionExceptionHandlers
141-
public void handleTestException(final MethodContext context, final Throwable error) {
142-
processTestException(context, getReversedExtensions(context, TestExecutionExceptionHandler.class), error);
169+
public void handleCleanupSpecMethodException(final AbstractContext context, final Throwable throwable) {
170+
invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, context.getRegistry(),
171+
throwable, (handler, handledThrowable) -> handler
172+
.handleAfterAllMethodExecutionException(context, handledThrowable));
143173
}
144174

145175
private <T extends Extension> List<T> getReversedExtensions(final AbstractContext context, final Class<T> type) {
@@ -165,29 +195,56 @@ private <T extends Extension> List<T> getExtensions(final AbstractContext contex
165195
return exts;
166196
}
167197

168-
private void executeAndMaskThrowable(final Executable executable) {
169-
try {
170-
executable.execute();
171-
} catch (Throwable throwable) {
172-
ExceptionUtils.throwAsUncheckedException(throwable);
173-
}
198+
/**
199+
* Invoke exception handlers for the supplied {@code Throwable} one-by-one
200+
* until none are left or the throwable to handle has been swallowed.
201+
*/
202+
// org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.invokeExecutionExceptionHandlers
203+
private <E extends Extension> void invokeExecutionExceptionHandlers(
204+
final Class<E> handlerType,
205+
final ExtensionRegistry registry,
206+
final Throwable throwable,
207+
final ExceptionHandlerInvoker<E> handlerInvoker) {
208+
209+
final List<E> extensions = registry.getReversedExtensions(handlerType);
210+
invokeExecutionExceptionHandlers(extensions, throwable, handlerInvoker);
174211
}
175212

176213
// org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.invokeExecutionExceptionHandlers
177-
private void processTestException(final MethodContext context,
178-
final List<TestExecutionExceptionHandler> handlers,
179-
final Throwable error) {
214+
private <E extends Extension> void invokeExecutionExceptionHandlers(
215+
final List<E> exceptionHandlers,
216+
final Throwable throwable,
217+
final ExceptionHandlerInvoker<E> handlerInvoker) {
218+
180219
// No handlers left?
181-
if (handlers.isEmpty()) {
182-
ExceptionUtils.throwAsUncheckedException(error);
220+
if (exceptionHandlers.isEmpty()) {
221+
throw ExceptionUtils.throwAsUncheckedException(throwable);
183222
}
184223

185224
try {
186225
// Invoke next available handler
187-
handlers.remove(0).handleTestExecutionException(context, error);
226+
handlerInvoker.invoke(exceptionHandlers.remove(0), throwable);
188227
} catch (Throwable handledThrowable) {
189228
UnrecoverableExceptions.rethrowIfUnrecoverable(handledThrowable);
190-
processTestException(context, handlers, handledThrowable);
229+
invokeExecutionExceptionHandlers(exceptionHandlers, handledThrowable, handlerInvoker);
191230
}
192231
}
232+
233+
private void executeAndMaskThrowable(final Executable executable) {
234+
try {
235+
executable.execute();
236+
} catch (Throwable throwable) {
237+
ExceptionUtils.throwAsUncheckedException(throwable);
238+
}
239+
}
240+
241+
@FunctionalInterface
242+
interface ExceptionHandlerInvoker<E extends Extension> {
243+
244+
/**
245+
* Invoke the supplied {@code exceptionHandler} with the supplied {@code throwable}.
246+
*/
247+
void invoke(E exceptionHandler, Throwable throwable) throws Throwable;
248+
249+
}
193250
}

src/test/groovy/ru/vyarus/spock/jupiter/SpockErrorsTest.groovy

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import playground.tests.exceptions.JupiterPreDestroyError
1313
import playground.tests.exceptions.JupiterTestError
1414
import playground.tests.exceptions.JupiterTestExceptionHandler
1515
import playground.tests.exceptions.JupiterTestExceptionHandler2
16+
import playground.tests.exceptions.JupiterTestExceptionHandler3
1617
import ru.vyarus.spock.jupiter.test.exception.*
1718

1819
/**
@@ -211,7 +212,7 @@ class SpockErrorsTest extends AbstractTest {
211212
"BeforeEachCallback",
212213
"BeforeTestExecutionCallback",
213214
"RethrowExceptionHandler problem",
214-
"SwallowExceptionHandler problem",
215+
"EatExceptionHandler problem",
215216
"AfterTestExecutionCallback",
216217
"AfterEachCallback",
217218
"AfterAllCallback"]
@@ -220,17 +221,35 @@ class SpockErrorsTest extends AbstractTest {
220221
def "Check exception handler on assertion"() {
221222

222223
expect: 'assertion swallowed'
223-
runTest(JupiterTestExceptionHandler2, SpockTestExceptionHandler2)
224+
runTestWithVerification(JupiterTestExceptionHandler2, SpockTestExceptionHandler2,
225+
"assert fail", "Condition not satisfied:\n\nfalse\n")
226+
224227
== ["BeforeAllCallback",
225228
"BeforeEachCallback",
226229
"BeforeTestExecutionCallback",
227230
"RethrowExceptionHandler Condition not satisfied:\n\nfalse\n",
228-
"SwallowExceptionHandler Condition not satisfied:\n\nfalse\n",
231+
"EatExceptionHandler Condition not satisfied:\n\nfalse\n",
229232
"AfterTestExecutionCallback",
230233
"AfterEachCallback",
231234
"AfterAllCallback"]
232235
}
233236

237+
def "Check exception handler rethrows"() {
238+
239+
expect: 'assertion swallowed'
240+
runTestWithVerification(JupiterTestExceptionHandler3, SpockTestExceptionHandler3,
241+
"assert fail", "Condition not satisfied:\n\nfalse\n",
242+
"AssertionFailedError", "ConditionNotSatisfiedError")
243+
== ["BeforeAllCallback",
244+
"BeforeEachCallback",
245+
"BeforeTestExecutionCallback",
246+
"RethrowExceptionHandler Condition not satisfied:\n\nfalse\n",
247+
"AfterTestExecutionCallback",
248+
"AfterEachCallback",
249+
"AfterAllCallback",
250+
"Error: (ConditionNotSatisfiedError) Condition not satisfied:\n\nfalse\n"]
251+
}
252+
234253
def "Check executable invoker error"() {
235254

236255
expect: 'executable invoker call failed'
@@ -240,7 +259,7 @@ class SpockErrorsTest extends AbstractTest {
240259
def "Check no supported extension found"() {
241260

242261
expect: 'error'
243-
runTest(SpockNotSupportedExtensions) == ["Error: (IllegalStateException) Extension ru.vyarus.spock.jupiter.test.exception.SpockNotSupportedExtensions\$NotSupportedExtension does not use any of supported extension types: ExecutionCondition, BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver, TestInstancePostProcessor, TestInstancePreDestroyCallback, TestExecutionExceptionHandler"]
262+
runTest(SpockNotSupportedExtensions) == ["Error: (IllegalStateException) Extension ru.vyarus.spock.jupiter.test.exception.SpockNotSupportedExtensions\$NotSupportedExtension does not use any of supported extension types: ExecutionCondition, BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver, TestInstancePostProcessor, TestInstancePreDestroyCallback, TestExecutionExceptionHandler, LifecycleMethodExecutionExceptionHandler"]
244263
}
245264

246265
def "Check extension with both supported and not supported extensions"() {

0 commit comments

Comments
 (0)