Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cucumber-java-lambda #2542

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions java-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Cucumber Java8
==============

Provides lambda based step definitions. To use add the `cucumber-java-lambda` dependency to your pom.xml:

```xml
<dependencies>
[...]
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java-lambda</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
```

## Step Definitions

## Hooks

## Transformers

### Parameter Type

### Data Table Type

#### Default Transformers

#### Empty Cells

#### Transposing Tables

### DocString type
80 changes: 80 additions & 0 deletions java-lambda/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm</artifactId>
<version>7.3.3-SNAPSHOT</version>
</parent>

<artifactId>cucumber-java-lambda</artifactId>
<packaging>jar</packaging>
<name>Cucumber-JVM: Java Lambda</name>

<properties>
<project.Automatic-Module-Name>io.cucumber.lambda</project.Automatic-Module-Name>
<apiguardian-api.version>1.1.2</apiguardian-api.version>
<junit-jupiter.version>5.8.2</junit-jupiter.version>
<typetools.version>0.6.3</typetools.version>
<assertj.version>3.22.0</assertj.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit-jupiter.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
</dependency>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
<version>${apiguardian-api.version}</version>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>typetools</artifactId>
<version>${typetools.version}</version>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
</dependency>
</dependencies>

</project>
30 changes: 30 additions & 0 deletions java-lambda/src/main/java/io/cucumber/lambda/Invoker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.cucumber.lambda;

import io.cucumber.core.backend.CucumberBackendException;
import io.cucumber.core.backend.CucumberInvocationTargetException;
import io.cucumber.core.backend.Located;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

final class Invoker {

private Invoker() {

}

static Object invoke(Located located, Object target, Method method, Object... args) {
boolean accessible = method.canAccess(target);
try {
method.setAccessible(true);
return method.invoke(target, args);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new CucumberBackendException("Failed to invoke " + located.getLocation(), e);
} catch (InvocationTargetException e) {
throw new CucumberInvocationTargetException(located, e);
} finally {
method.setAccessible(accessible);
}
}

}
77 changes: 77 additions & 0 deletions java-lambda/src/main/java/io/cucumber/lambda/StepDeclaration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.cucumber.lambda;

import io.cucumber.core.backend.Located;
import io.cucumber.core.backend.SourceReference;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static io.cucumber.core.backend.SourceReference.fromStackTraceElement;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

class StepDeclaration implements Located {
private final String expression;
private final Class<?> context;
private final StepDefinitionFunction function;
private final StackTraceElement location;
private SourceReference sourceReference;

StepDeclaration(String expression, Class<?> context, StepDefinitionFunction function, StackTraceElement location) {
this.expression = requireNonNull(expression, "expression should not be null");
this.context = requireNonNull(context, "context should not be null");
this.function = requireNonNull(function, "function should not be null");
this.location = requireNonNull(location, "location should not be null");

}

String getExpression() {
return expression;
}

Class<?> getContext() {
return context;
}

@Override
public final boolean isDefinedAt(StackTraceElement stackTraceElement) {
return location.getFileName() != null && location.getFileName().equals(stackTraceElement.getFileName());
}

@Override
public Optional<SourceReference> getSourceReference() {
if (sourceReference == null) {
sourceReference = fromStackTraceElement(location);
}
return Optional.of(sourceReference);
}

@Override
public final String getLocation() {
return location.toString();
}

void invoke(Object contextInstance, Object[] arguments) {
Method acceptMethod = getAcceptMethod(function.getClass());
Object body = Invoker.invoke(this, function, acceptMethod, contextInstance);
Method bodyAcceptMethod = getAcceptMethod(body.getClass());
Invoker.invoke(this, body, bodyAcceptMethod, arguments);
}

private Method getAcceptMethod(Class<?> bodyClass) {
List<Method> acceptMethods = new ArrayList<>();
for (Method method : bodyClass.getDeclaredMethods()) {
if (!method.isBridge() && !method.isSynthetic() && "accept".equals(method.getName())) {
acceptMethods.add(method);
}
}
if (acceptMethods.size() != 1) {
throw new IllegalStateException(format(
"Expected single 'accept' method on body class, found '%s'", acceptMethods));
}
return acceptMethods.get(0);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package io.cucumber.lambda;

import org.apiguardian.api.API;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

@API(status = EXPERIMENTAL, since = "7.4.0")
public interface StepDefinitionFunction {

@FunctionalInterface
interface C0A0 extends StepDefinitionFunction {
StepDefinitionBody.A0 accept();

}

@FunctionalInterface
interface C1A0<Context> extends StepDefinitionFunction {
StepDefinitionBody.A0 accept(Context context);

}

@FunctionalInterface
interface C1A1<Context, T1> extends StepDefinitionFunction {
StepDefinitionBody.A1<T1> accept(Context context);

}

@FunctionalInterface
interface C1A2<Context, T1, T2> extends StepDefinitionFunction {
StepDefinitionBody.A2<T1, T2> accept(Context context);

}

@FunctionalInterface
interface C1A3<Context, T1, T2, A3> extends StepDefinitionFunction {
StepDefinitionBody.A3<T1, T2, A3> accept(Context context);

}

@FunctionalInterface
interface C1A4<Context, T1, T2, T3, T4> extends StepDefinitionFunction {
StepDefinitionBody.A4<T1, T2, T3, T4> accept(Context context);

}

@FunctionalInterface
interface C1A5<Context, T1, T2, T3, T4, T5> extends StepDefinitionFunction {
StepDefinitionBody.A5<T1, T2, T3, T4, T5> accept(Context context);

}

@FunctionalInterface
interface C1A6<Context, T1, T2, T3, T4, T5, T6> extends StepDefinitionFunction {
StepDefinitionBody.A6<T1, T2, T3, T4, T5, T6> accept(Context context);

}

@FunctionalInterface
interface C1A7<Context, T1, T2, T3, T4, T5, T6, T7> extends StepDefinitionFunction {
StepDefinitionBody.A7<T1, T2, T3, T4, T5, T6, T7> accept(Context context);

}

@FunctionalInterface
interface C1A8<Context, T1, T2, T3, T4, T5, T6, T7, T8> extends StepDefinitionFunction {
StepDefinitionBody.A8<T1, T2, T3, T4, T5, T6, T7, T8> accept(Context context);

}

@FunctionalInterface
interface C1A9<Context, T1, T2, T3, T4, T5, T6, T7, T8, T9> extends StepDefinitionFunction {
StepDefinitionBody.A9<T1, T2, T3, T4, T5, T6, T7, T8, T9> accept(Context context);

}

interface StepDefinitionBody {

@FunctionalInterface
interface A0 extends StepDefinitionBody {
void accept() throws Throwable;

}

@FunctionalInterface
interface A1<T1> extends StepDefinitionBody {
void accept(T1 t1) throws Throwable;

}

@FunctionalInterface
interface A2<T1, T2> extends StepDefinitionBody {
void accept(T1 t1, T2 t2) throws Throwable;

}

@FunctionalInterface
interface A3<T1, T2, T3> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3) throws Throwable;

}

@FunctionalInterface
interface A4<T1, T2, T3, T4> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3, T4 t4) throws Throwable;

}

@FunctionalInterface
interface A5<T1, T2, T3, T4, T5> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) throws Throwable;

}

@FunctionalInterface
interface A6<T1, T2, T3, T4, T5, T6> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) throws Throwable;

}

@FunctionalInterface
interface A7<T1, T2, T3, T4, T5, T6, T7> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) throws Throwable;

}

@FunctionalInterface
interface A8<T1, T2, T3, T4, T5, T6, T7, T8> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) throws Throwable;

}

@FunctionalInterface
interface A9<T1, T2, T3, T4, T5, T6, T7, T8, T9> extends StepDefinitionBody {
void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) throws Throwable;

}

}

}
Loading