Skip to content

Commit 79f1cf9

Browse files
committed
Include root cause when using DataTable.asList and friends
The `CucumberInvocationTargetException` was originally designed to be used as a wrapper around `InvocationTargetException` to track the step definition that caused the exception. The cause of `InvocationTargetException` would then be rewritten to appear to have originated from the step definition. This would effectively hide the whole backend from view. Unfortunately using DataTable.asList inside a step, this also invokes the backend. And because `CucumberInvocationTargetException` did not have cause, the cause of any exceptions would also be hidden. By exposing the cause of the `InvocationTargetException` as the cause of the `CucumberInvocationTargetException` and removing any frames before the step definition was invoked we can clean this up somewhat. ```log io.cucumber.datatable.CucumberDataTableException: 'java.util.List<io.cucumber.skeleton.StepDefinitions$Ingredient>' could not transform | NAME | QUANTITY | UNITS | | Flour | 1 | KG | | Water | 0.65 | L | | Salt | 0.08 | KG | at io.cucumber.datatable.DataTableType.transform(DataTableType.java:158) at io.cucumber.datatable.DataTableTypeRegistryTableConverter.toListOrProblems(DataTableTypeRegistryTableConverter.java:158) at io.cucumber.datatable.DataTableTypeRegistryTableConverter.toList(DataTableTypeRegistryTableConverter.java:139) at io.cucumber.datatable.DataTable.asList(DataTable.java:199) at io.cucumber.skeleton.StepDefinitions.i_wait_hour(StepDefinitions.java:40) at ✽.I mix the following ingredients(classpath:io/cucumber/skeleton/belly.feature:6) Caused by: io.cucumber.core.backend.CucumberInvocationTargetException at io.cucumber.java.Invoker.doInvoke(Invoker.java:73) at io.cucumber.java.Invoker.invoke(Invoker.java:24) at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) at io.cucumber.java.JavaDataTableTypeDefinition.lambda$createDataTableType$2(JavaDataTableTypeDefinition.java:47) at io.cucumber.datatable.DataTableType$TableEntryTransformerAdaptor.transform(DataTableType.java:342) at io.cucumber.datatable.DataTableType$TableEntryTransformerAdaptor.transform(DataTableType.java:319) at io.cucumber.datatable.DataTableType.transform(DataTableType.java:155) ... 5 more Caused by: java.lang.IllegalArgumentException: This message is never shown during test execution at io.cucumber.skeleton.StepDefinitions.mySystemEntry(StepDefinitions.java:29) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ... 11 more ``` Unfortunately, it is not possible to remove the `CucumberInvocationTargetException` entirely. Fixes: #2948
1 parent be27cca commit 79f1cf9

File tree

6 files changed

+65
-43
lines changed

6 files changed

+65
-43
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Fixed
14+
- [Core] Include root cause when using DataTable.asList and friends ([#2949](https://github.com/cucumber/cucumber-jvm/pull/2949) M.P. Korstanje)
1315

1416
### Changed
1517
- [JUnit Platform Engine] Use JUnit Platform 1.11.3 (JUnit Jupiter 5.11.3)

cucumber-core/src/main/java/io/cucumber/core/backend/CucumberInvocationTargetException.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@ public CucumberInvocationTargetException(Located located, InvocationTargetExcept
2020
this.invocationTargetException = invocationTargetException;
2121
}
2222

23+
/**
24+
* @deprecated use {@link #getCause()} instead.
25+
*/
26+
@Deprecated
2327
public Throwable getInvocationTargetExceptionCause() {
24-
return invocationTargetException.getCause();
28+
return getCause();
2529
}
2630

2731
public Located getLocated() {
2832
return located;
2933
}
3034

35+
@Override
36+
public Throwable getCause() {
37+
return invocationTargetException.getCause();
38+
}
3139
}

cucumber-core/src/main/java/io/cucumber/core/runner/StackManipulation.java

+51-39
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,72 @@
33
import io.cucumber.core.backend.CucumberInvocationTargetException;
44
import io.cucumber.core.backend.Located;
55

6+
import java.util.function.Consumer;
7+
68
final class StackManipulation {
79

810
private StackManipulation() {
911

1012
}
1113

12-
static Throwable removeFrameworkFrames(CucumberInvocationTargetException invocationException) {
13-
Throwable error = invocationException.getInvocationTargetExceptionCause();
14-
StackTraceElement[] stackTraceElements = error.getStackTrace();
15-
Located located = invocationException.getLocated();
16-
17-
int newStackTraceLength = findIndexOf(located, stackTraceElements);
18-
if (newStackTraceLength == -1) {
19-
return error;
20-
}
14+
static Throwable removeFrameworkFramesAndAppendStepLocation(
15+
CucumberInvocationTargetException invocationException, StackTraceElement stepLocation
16+
) {
17+
Throwable error = invocationException.getCause();
18+
walkException(error, appendStepLocation(invocationException.getLocated(), stepLocation));
19+
return error;
20+
}
2121

22-
StackTraceElement[] newStackTrace = new StackTraceElement[newStackTraceLength];
23-
System.arraycopy(stackTraceElements, 0, newStackTrace, 0, newStackTraceLength);
24-
error.setStackTrace(newStackTrace);
22+
static Throwable removeFrameworkFrames(CucumberInvocationTargetException invocationException) {
23+
Throwable error = invocationException.getCause();
24+
walkException(invocationException, removeFramesAfter(invocationException.getLocated()));
2525
return error;
2626
}
2727

28-
private static int findIndexOf(Located located, StackTraceElement[] stackTraceElements) {
29-
if (stackTraceElements.length == 0) {
30-
return -1;
28+
private static void walkException(Throwable cause, Consumer<Throwable> action) {
29+
while (cause != null) {
30+
action.accept(cause);
31+
cause = cause.getCause();
3132
}
33+
}
3234

33-
int newStackTraceLength;
34-
for (newStackTraceLength = 1; newStackTraceLength < stackTraceElements.length; ++newStackTraceLength) {
35-
if (located.isDefinedAt(stackTraceElements[newStackTraceLength - 1])) {
36-
break;
35+
static Consumer<Throwable> removeFramesAfter(Located located) {
36+
return throwable -> {
37+
StackTraceElement[] stackTrace = throwable.getStackTrace();
38+
int lastFrame = findIndexOf(located, stackTrace);
39+
if (lastFrame == -1) {
40+
return;
3741
}
38-
}
39-
return newStackTraceLength;
42+
StackTraceElement[] newStackTrace = new StackTraceElement[lastFrame + 1];
43+
System.arraycopy(stackTrace, 0, newStackTrace, 0, lastFrame + 1);
44+
throwable.setStackTrace(newStackTrace);
45+
};
4046
}
4147

42-
static Throwable removeFrameworkFramesAndAppendStepLocation(
43-
CucumberInvocationTargetException invocationException, StackTraceElement stepLocation
44-
) {
45-
Located located = invocationException.getLocated();
46-
Throwable error = invocationException.getInvocationTargetExceptionCause();
47-
if (stepLocation == null) {
48-
return error;
49-
}
50-
StackTraceElement[] stackTraceElements = error.getStackTrace();
51-
int newStackTraceLength = findIndexOf(located, stackTraceElements);
52-
if (newStackTraceLength == -1) {
53-
return error;
54-
}
55-
StackTraceElement[] newStackTrace = new StackTraceElement[newStackTraceLength + 1];
56-
System.arraycopy(stackTraceElements, 0, newStackTrace, 0, newStackTraceLength);
57-
newStackTrace[newStackTraceLength] = stepLocation;
58-
error.setStackTrace(newStackTrace);
59-
return error;
48+
private static Consumer<Throwable> appendStepLocation(Located located, StackTraceElement stepLocation) {
49+
return throwable -> {
50+
if (located == null) {
51+
return;
52+
}
53+
StackTraceElement[] stackTrace = throwable.getStackTrace();
54+
int lastFrame = findIndexOf(located, stackTrace);
55+
if (lastFrame == -1) {
56+
return;
57+
}
58+
// One extra for the step location
59+
StackTraceElement[] newStackTrace = new StackTraceElement[lastFrame + 1 + 1];
60+
System.arraycopy(stackTrace, 0, newStackTrace, 0, lastFrame + 1);
61+
newStackTrace[lastFrame + 1] = stepLocation;
62+
throwable.setStackTrace(newStackTrace);
63+
};
6064
}
6165

66+
private static int findIndexOf(Located located, StackTraceElement[] stackTraceElements) {
67+
for (int index = 0; index < stackTraceElements.length; index++) {
68+
if (located.isDefinedAt(stackTraceElements[index])) {
69+
return index;
70+
}
71+
}
72+
return -1;
73+
}
6274
}

cucumber-core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class StepDefinitionMatchTest {
4646
private final Located stubbedLocation = new Located() {
4747
@Override
4848
public boolean isDefinedAt(StackTraceElement stackTraceElement) {
49-
return false;
49+
return true;
5050
}
5151

5252
@Override

cucumber-java/src/test/java/io/cucumber/java/JavaStepDefinitionTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ void can_provide_location_of_step() throws Throwable {
4444
JavaStepDefinition definition = new JavaStepDefinition(method, "three (.*) mice", lookup);
4545
CucumberInvocationTargetException exception = assertThrows(CucumberInvocationTargetException.class,
4646
() -> definition.execute(new Object[0]));
47-
Optional<StackTraceElement> match = stream(exception.getInvocationTargetExceptionCause().getStackTrace())
47+
Optional<StackTraceElement> match = stream(exception.getCause().getStackTrace())
4848
.filter(definition::isDefinedAt).findFirst();
4949
StackTraceElement stackTraceElement = match.get();
5050

cucumber-java8/src/test/java/io/cucumber/java8/Java8LambdaStepDefinitionMarksCorrectStackElementTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ void exception_from_step_should_be_defined_at_step_definition_class() {
2929

3030
CucumberInvocationTargetException exception = assertThrows(CucumberInvocationTargetException.class,
3131
() -> stepDefinition.execute(new Object[0]));
32-
assertThat(exception.getInvocationTargetExceptionCause(),
32+
assertThat(exception.getCause(),
3333
new CustomTypeSafeMatcher<Throwable>("exception with matching stack trace") {
3434
@Override
3535
protected boolean matchesSafely(Throwable item) {

0 commit comments

Comments
 (0)