Skip to content

Commit e88b849

Browse files
committed
Include SourceReferences in message output
Cucumber JVM can not reference files by URI. So it should use either java methods or stack trace elements as a source reference instead. For example: ``` { "hook": { "id": "a1839ec6-f75d-4029-9b75-4b8203d8b2e8", "sourceReference": { "javaMethod": { "className": "io.cucumber.compatibility.attachments.Attachments", "methodName": "before", "methodParameterTypes": [ "io.cucumber.java.Scenario" ] } } } } ``` See: cucumber/common#1119 Fixes: #2058
1 parent 239460b commit e88b849

File tree

12 files changed

+223
-15
lines changed

12 files changed

+223
-15
lines changed

compatibility/src/test/java/io/cucumber/compatibility/matchers/AComparableMessage.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ private static List<Matcher<?>> extractExpectedFields(GeneratedMessageV3 expecte
5050
expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass())));
5151
break;
5252

53-
// exception: the CCK expects source references but java can not
54-
// provide them
53+
// exception: the CCK expects source references with URIs but
54+
// Java can only provide method and stack trace references.
5555
case "sourceReference":
56-
expected.add(not(hasKey(is(fieldName))));
56+
expected.add(hasKey(is(fieldName)));
5757
break;
5858

5959
// exception: ids are not predictable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.cucumber.core.backend;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Objects;
6+
7+
import static java.util.Objects.requireNonNull;
8+
9+
public final class JavaMethodReference implements SourceReference {
10+
11+
private final String className;
12+
private final String methodName;
13+
private final List<String> methodParameterTypes;
14+
15+
JavaMethodReference(Class<?> declaringClass, String methodName, Class<?>[] methodParameterTypes) {
16+
this.className = requireNonNull(declaringClass).getName();
17+
this.methodName = requireNonNull(methodName);
18+
this.methodParameterTypes = new ArrayList<>(methodParameterTypes.length);
19+
for (Class<?> parameterType : methodParameterTypes) {
20+
this.methodParameterTypes.add(parameterType.getName());
21+
}
22+
}
23+
24+
public String className() {
25+
return className;
26+
}
27+
28+
public String methodName() {
29+
return methodName;
30+
}
31+
32+
public List<String> methodParameterTypes() {
33+
return methodParameterTypes;
34+
}
35+
36+
@Override
37+
public boolean equals(Object o) {
38+
if (this == o)
39+
return true;
40+
if (o == null || getClass() != o.getClass())
41+
return false;
42+
JavaMethodReference that = (JavaMethodReference) o;
43+
return className.equals(that.className) &&
44+
methodName.equals(that.methodName) &&
45+
methodParameterTypes.equals(that.methodParameterTypes);
46+
}
47+
48+
@Override
49+
public int hashCode() {
50+
return Objects.hash(className, methodName, methodParameterTypes);
51+
}
52+
53+
}

core/src/main/java/io/cucumber/core/backend/Located.java

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.apiguardian.api.API;
44

5+
import java.util.Optional;
6+
57
@API(status = API.Status.STABLE)
68
public interface Located {
79

@@ -29,4 +31,7 @@ public interface Located {
2931
*/
3032
String getLocation();
3133

34+
default Optional<SourceReference> getSourceReference() {
35+
return Optional.empty();
36+
}
3237
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.cucumber.core.backend;
2+
3+
import java.lang.reflect.Method;
4+
5+
public interface SourceReference {
6+
7+
static SourceReference fromMethod(Method method) {
8+
return new JavaMethodReference(
9+
method.getDeclaringClass(),
10+
method.getName(),
11+
method.getParameterTypes());
12+
}
13+
14+
static SourceReference fromStackTraceElement(StackTraceElement stackTraceElement) {
15+
return new StackTraceElementReference(
16+
stackTraceElement.getClassName(),
17+
stackTraceElement.getMethodName(),
18+
stackTraceElement.getFileName(),
19+
stackTraceElement.getLineNumber());
20+
}
21+
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.cucumber.core.backend;
2+
3+
import java.util.Objects;
4+
import java.util.Optional;
5+
6+
import static java.util.Objects.requireNonNull;
7+
8+
public class StackTraceElementReference implements SourceReference {
9+
10+
private final String className;
11+
private final String methodName;
12+
private final String fileName;
13+
private final int lineNumber;
14+
15+
StackTraceElementReference(String className, String methodName, String fileName, int lineNumber) {
16+
this.className = requireNonNull(className);
17+
this.methodName = requireNonNull(methodName);
18+
this.fileName = fileName;
19+
this.lineNumber = lineNumber;
20+
}
21+
22+
public String className() {
23+
return className;
24+
}
25+
26+
public String methodName() {
27+
return methodName;
28+
}
29+
30+
public Optional<String> fileName() {
31+
return Optional.ofNullable(fileName);
32+
}
33+
34+
public int lineNumber() {
35+
return lineNumber;
36+
}
37+
38+
@Override
39+
public boolean equals(Object o) {
40+
if (this == o)
41+
return true;
42+
if (o == null || getClass() != o.getClass())
43+
return false;
44+
StackTraceElementReference that = (StackTraceElementReference) o;
45+
return lineNumber == that.lineNumber &&
46+
className.equals(that.className) &&
47+
methodName.equals(that.methodName) &&
48+
Objects.equals(fileName, that.fileName);
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return Objects.hash(className, methodName, fileName, lineNumber);
54+
}
55+
56+
}

core/src/main/java/io/cucumber/core/runner/CachingGlue.java

+48-10
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import io.cucumber.core.backend.DocStringTypeDefinition;
88
import io.cucumber.core.backend.Glue;
99
import io.cucumber.core.backend.HookDefinition;
10+
import io.cucumber.core.backend.JavaMethodReference;
1011
import io.cucumber.core.backend.ParameterTypeDefinition;
1112
import io.cucumber.core.backend.ScenarioScoped;
13+
import io.cucumber.core.backend.StackTraceElementReference;
1214
import io.cucumber.core.backend.StepDefinition;
1315
import io.cucumber.core.eventbus.EventBus;
1416
import io.cucumber.core.gherkin.Step;
@@ -24,6 +26,12 @@
2426
import io.cucumber.datatable.TableCellByTypeTransformer;
2527
import io.cucumber.datatable.TableEntryByTypeTransformer;
2628
import io.cucumber.messages.Messages;
29+
import io.cucumber.messages.Messages.Envelope;
30+
import io.cucumber.messages.Messages.Hook;
31+
import io.cucumber.messages.Messages.JavaStackTraceElement;
32+
import io.cucumber.messages.Messages.Location;
33+
import io.cucumber.messages.Messages.SourceReference;
34+
import io.cucumber.messages.Messages.StepDefinition.Builder;
2735
import io.cucumber.messages.Messages.StepDefinition.StepDefinitionPattern;
2836
import io.cucumber.messages.Messages.StepDefinition.StepDefinitionPattern.StepDefinitionPatternType;
2937
import io.cucumber.plugin.event.StepDefinedEvent;
@@ -259,10 +267,13 @@ private void emitParameterTypeDefined(ParameterType<?> parameterType) {
259267
}
260268

261269
private void emitHook(CoreHookDefinition hook) {
270+
Hook.Builder hookDefinitionBuilder = Hook.newBuilder()
271+
.setId(hook.getId().toString())
272+
.setTagExpression(hook.getTagExpression());
273+
hook.getDefinitionLocation()
274+
.ifPresent(reference -> hookDefinitionBuilder.setSourceReference(createSourceReference(reference)));
262275
bus.send(Messages.Envelope.newBuilder()
263-
.setHook(Messages.Hook.newBuilder()
264-
.setId(hook.getId().toString())
265-
.setTagExpression(hook.getTagExpression()))
276+
.setHook(hookDefinitionBuilder)
266277
.build());
267278
}
268279

@@ -272,16 +283,43 @@ private void emitStepDefined(CoreStepDefinition stepDefinition) {
272283
new io.cucumber.plugin.event.StepDefinition(
273284
stepDefinition.getStepDefinition().getLocation(),
274285
stepDefinition.getExpression().getSource())));
275-
bus.send(Messages.Envelope.newBuilder()
276-
.setStepDefinition(
277-
Messages.StepDefinition.newBuilder()
278-
.setId(stepDefinition.getId().toString())
279-
.setPattern(StepDefinitionPattern.newBuilder()
280-
.setSource(stepDefinition.getExpression().getSource())
281-
.setType(getExpressionType(stepDefinition))))
286+
287+
Builder stepDefinitionBuilder = Messages.StepDefinition.newBuilder()
288+
.setId(stepDefinition.getId().toString())
289+
.setPattern(StepDefinitionPattern.newBuilder()
290+
.setSource(stepDefinition.getExpression().getSource())
291+
.setType(getExpressionType(stepDefinition)));
292+
stepDefinition.getDefinitionLocation()
293+
.ifPresent(reference -> stepDefinitionBuilder.setSourceReference(createSourceReference(reference)));
294+
bus.send(Envelope.newBuilder()
295+
.setStepDefinition(stepDefinitionBuilder)
282296
.build());
283297
}
284298

299+
private SourceReference.Builder createSourceReference(io.cucumber.core.backend.SourceReference reference) {
300+
SourceReference.Builder sourceReferenceBuilder = SourceReference.newBuilder();
301+
if (reference instanceof JavaMethodReference) {
302+
JavaMethodReference methodReference = (JavaMethodReference) reference;
303+
sourceReferenceBuilder.setJavaMethod(Messages.JavaMethod.newBuilder()
304+
.setClassName(methodReference.className())
305+
.setMethodName(methodReference.methodName())
306+
.addAllMethodParameterTypes(methodReference.methodParameterTypes()));
307+
}
308+
309+
if (reference instanceof StackTraceElementReference) {
310+
StackTraceElementReference stackReference = (StackTraceElementReference) reference;
311+
JavaStackTraceElement.Builder stackTraceElementBuilder = JavaStackTraceElement.newBuilder()
312+
.setClassName(stackReference.className())
313+
.setMethodName(stackReference.methodName());
314+
stackReference.fileName().ifPresent(stackTraceElementBuilder::setFileName);
315+
sourceReferenceBuilder
316+
.setJavaStackTraceElement(stackTraceElementBuilder)
317+
.setLocation(Location.newBuilder()
318+
.setLine(stackReference.lineNumber()));
319+
}
320+
return sourceReferenceBuilder;
321+
}
322+
285323
private StepDefinitionPatternType getExpressionType(CoreStepDefinition stepDefinition) {
286324
Class<? extends Expression> expressionType = stepDefinition.getExpression().getExpressionType();
287325
if (expressionType.isAssignableFrom(RegularExpression.class)) {

core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java

+5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import io.cucumber.core.backend.HookDefinition;
44
import io.cucumber.core.backend.ScenarioScoped;
5+
import io.cucumber.core.backend.SourceReference;
56
import io.cucumber.core.backend.TestCaseState;
67
import io.cucumber.tagexpressions.Expression;
78
import io.cucumber.tagexpressions.TagExpressionException;
89
import io.cucumber.tagexpressions.TagExpressionParser;
910

1011
import java.util.List;
12+
import java.util.Optional;
1113
import java.util.UUID;
1214

1315
import static java.util.Objects.requireNonNull;
@@ -84,4 +86,7 @@ public void dispose() {
8486

8587
}
8688

89+
Optional<SourceReference> getDefinitionLocation() {
90+
return delegate.getSourceReference();
91+
}
8792
}

core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.cucumber.core.backend.CucumberBackendException;
44
import io.cucumber.core.backend.CucumberInvocationTargetException;
55
import io.cucumber.core.backend.ParameterInfo;
6+
import io.cucumber.core.backend.SourceReference;
67
import io.cucumber.core.backend.StepDefinition;
78
import io.cucumber.core.gherkin.Step;
89
import io.cucumber.core.stepexpression.Argument;
@@ -11,6 +12,7 @@
1112

1213
import java.lang.reflect.Type;
1314
import java.util.List;
15+
import java.util.Optional;
1416
import java.util.UUID;
1517

1618
import static java.util.Objects.requireNonNull;
@@ -84,4 +86,8 @@ public String getLocation() {
8486
return stepDefinition.getLocation();
8587
}
8688

89+
Optional<SourceReference> getDefinitionLocation() {
90+
return stepDefinition.getSourceReference();
91+
}
92+
8793
}

core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ void emits_a_meta_message() {
506506
.run();
507507

508508
Messages.Meta meta = messages.get(0).getMeta();
509-
assertThat(meta.getProtocolVersion(), matchesPattern("\\d+\\.\\d+\\.\\d+"));
509+
assertThat(meta.getProtocolVersion(), matchesPattern("\\d+\\.\\d+\\.\\d+(-RC\\d+)?(-SNAPSHOT)?"));
510510
assertThat(meta.getImplementation().getName(), is("cucumber-jvm"));
511511
assertThat(meta.getImplementation().getVersion(), matchesPattern("\\d+\\.\\d+\\.\\d+(-RC\\d+)?(-SNAPSHOT)?"));
512512
assertThat(meta.getOs().getName(), matchesPattern(".+"));

java/src/main/java/io/cucumber/java/AbstractGlueDefinition.java

+11
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import io.cucumber.core.backend.Located;
44
import io.cucumber.core.backend.Lookup;
5+
import io.cucumber.core.backend.SourceReference;
56

67
import java.lang.reflect.Method;
78
import java.lang.reflect.Modifier;
9+
import java.util.Optional;
810

911
import static java.util.Objects.requireNonNull;
1012

@@ -13,6 +15,7 @@ abstract class AbstractGlueDefinition implements Located {
1315
protected final Method method;
1416
private final Lookup lookup;
1517
private String fullFormat;
18+
private SourceReference sourceReference;
1619

1720
AbstractGlueDefinition(Method method, Lookup lookup) {
1821
this.method = requireNonNull(method);
@@ -44,4 +47,12 @@ final Object invokeMethod(Object... args) {
4447
return Invoker.invoke(this, lookup.getInstance(method.getDeclaringClass()), method, args);
4548
}
4649

50+
@Override
51+
public Optional<SourceReference> getSourceReference() {
52+
if (sourceReference == null) {
53+
sourceReference = SourceReference.fromMethod(this.method);
54+
}
55+
return Optional.of(sourceReference);
56+
}
57+
4758
}

java8/src/main/java/io/cucumber/java8/AbstractGlueDefinition.java

+12
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import io.cucumber.core.backend.Located;
44
import io.cucumber.core.backend.ScenarioScoped;
5+
import io.cucumber.core.backend.SourceReference;
56
import net.jodah.typetools.TypeResolver;
67

78
import java.lang.reflect.Method;
89
import java.util.ArrayList;
910
import java.util.List;
11+
import java.util.Optional;
1012

13+
import static io.cucumber.core.backend.SourceReference.fromStackTraceElement;
1114
import static java.lang.String.format;
1215
import static java.util.Objects.requireNonNull;
1316

@@ -16,6 +19,7 @@ abstract class AbstractGlueDefinition implements ScenarioScoped, Located {
1619
private Object body;
1720
final Method method;
1821
final StackTraceElement location;
22+
SourceReference sourceReference;
1923

2024
AbstractGlueDefinition(Object body, StackTraceElement location) {
2125
this.body = requireNonNull(body);
@@ -59,6 +63,14 @@ public final boolean isDefinedAt(StackTraceElement stackTraceElement) {
5963
return location.getFileName() != null && location.getFileName().equals(stackTraceElement.getFileName());
6064
}
6165

66+
@Override
67+
public Optional<SourceReference> getSourceReference() {
68+
if (sourceReference == null) {
69+
sourceReference = fromStackTraceElement(location);
70+
}
71+
return Optional.of(sourceReference);
72+
}
73+
6274
Class<?>[] resolveRawArguments(Class<?> bodyClass, Class<?> body) {
6375
Class<?>[] rawArguments = TypeResolver.resolveRawArguments(bodyClass, body);
6476
for (Class<?> aClass : rawArguments) {

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<datatable.version>3.3.1</datatable.version>
2828
<tag-expressions.version>3.0.0</tag-expressions.version>
2929
<!-- When ever messages is updated run `make update-compatibility` -->
30-
<messages.version>12.2.0</messages.version>
30+
<messages.version>12.3.2</messages.version>
3131
<gherkin.version>14.0.1</gherkin.version>
3232
<html-formatter.version>7.0.0</html-formatter.version>
3333

0 commit comments

Comments
 (0)