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

[Java] Add BeforeAll and AfterAll hooks #1876

Merged
merged 34 commits into from
Apr 3, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2f4e9da
[Java] Add BeforeAll and AfterAll hooks
mpkorstanje Jan 26, 2020
fa4907d
Merge remote-tracking branch 'origin/main' into before-all-and-after-…
mpkorstanje Feb 28, 2021
ac7ed17
Merge remote-tracking branch 'origin/main' into before-all-and-after-…
mpkorstanje Mar 8, 2021
c8d2a80
Require void return type and invoke without object
mpkorstanje Mar 8, 2021
fbaeeac
WIP Invoke around semantics
mpkorstanje Mar 8, 2021
c64cc35
JUnit Invoke around semantics
mpkorstanje Mar 8, 2021
950e158
JUnit Invoke around semantics
mpkorstanje Mar 8, 2021
91002b4
TestNG Invoke around semantics
mpkorstanje Mar 8, 2021
32b4d7b
Sort hooks
mpkorstanje Mar 8, 2021
b49a269
Reduce diff size
mpkorstanje Mar 8, 2021
bf1d23e
Experimental api
mpkorstanje Mar 8, 2021
32dc361
Merge remote-tracking branch 'origin/main' into before-all-and-after-…
mpkorstanje Mar 26, 2021
7cf19be
Reduce logspam
mpkorstanje Mar 26, 2021
b15778d
Reduce logspam
mpkorstanje Mar 26, 2021
a091372
Reduce logspam
mpkorstanje Mar 26, 2021
6c852fc
Display before/after hook failure in teamcity plugin
mpkorstanje Mar 26, 2021
314eeb7
Display before/after all failures in pretty formatter
mpkorstanje Mar 26, 2021
3518c59
Fix
mpkorstanje Mar 26, 2021
ce7967e
Fix
mpkorstanje Mar 26, 2021
2781758
TODO: Extract error collector and move up
mpkorstanje Mar 26, 2021
4b047fb
This works
mpkorstanje Mar 27, 2021
26b75ab
Improve the examples
mpkorstanje Mar 27, 2021
3d18b44
Merge remote-tracking branch 'origin/main' into before-all-and-after-…
mpkorstanje Mar 28, 2021
8f97461
Fix tests
mpkorstanje Mar 28, 2021
c6f3768
Fix tests
mpkorstanje Mar 28, 2021
419aaa7
Run after all hooks in reverse order
mpkorstanje Mar 28, 2021
01e9ca4
Run after all hooks in reverse order
mpkorstanje Mar 28, 2021
8eedf41
Use suppressed exceptions for better readability
mpkorstanje Apr 2, 2021
1b13fb3
Update documentation
mpkorstanje Apr 2, 2021
692870f
Merge remote-tracking branch 'origin/main' into before-all-and-after-…
mpkorstanje Apr 2, 2021
654a741
Update docs
mpkorstanje Apr 3, 2021
28f9d3c
Update tests
mpkorstanje Apr 3, 2021
5272080
Merge remote-tracking branch 'origin/v7.x.x' into before-all-and-afte…
mpkorstanje Apr 3, 2021
67889c2
Update changelog
mpkorstanje Apr 3, 2021
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
4 changes: 4 additions & 0 deletions core/src/main/java/io/cucumber/core/backend/Glue.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
@API(status = API.Status.STABLE)
public interface Glue {

void addBeforeAllHook(StaticHookDefinition beforeAllHook);

void addAfterAllHook(StaticHookDefinition afterAllHook);

void addStepDefinition(StepDefinition stepDefinition);

void addBeforeHook(HookDefinition beforeHook);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.cucumber.core.backend;

import org.apiguardian.api.API;

@API(status = API.Status.STABLE)
public interface StaticHookDefinition extends Located {

void execute();

int getOrder();
}
22 changes: 22 additions & 0 deletions core/src/main/java/io/cucumber/core/runner/CachingGlue.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.cucumber.core.backend.HookDefinition;
import io.cucumber.core.backend.ParameterTypeDefinition;
import io.cucumber.core.backend.ScenarioScoped;
import io.cucumber.core.backend.StaticHookDefinition;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.gherkin.Step;
Expand Down Expand Up @@ -45,11 +46,13 @@ final class CachingGlue implements Glue {
private final List<DefaultDataTableCellTransformerDefinition> defaultDataTableCellTransformers = new ArrayList<>();
private final List<DocStringTypeDefinition> docStringTypeDefinitions = new ArrayList<>();

private final List<StaticHookDefinition> beforeAllHooks = new ArrayList<>();
private final List<CoreHookDefinition> beforeHooks = new ArrayList<>();
private final List<CoreHookDefinition> beforeStepHooks = new ArrayList<>();
private final List<StepDefinition> stepDefinitions = new ArrayList<>();
private final List<CoreHookDefinition> afterStepHooks = new ArrayList<>();
private final List<CoreHookDefinition> afterHooks = new ArrayList<>();
private final List<StaticHookDefinition> afterAllHooks = new ArrayList<>();

/*
* Storing the pattern that matches the step text allows us to cache the rather slow
Expand All @@ -67,6 +70,17 @@ final class CachingGlue implements Glue {
this.bus = bus;
}

@Override
public void addBeforeAllHook(StaticHookDefinition beforeAllHook) {
beforeAllHooks.add(beforeAllHook);

}

@Override
public void addAfterAllHook(StaticHookDefinition afterAllHook) {
afterAllHooks.add(afterAllHook);
}

@Override
public void addStepDefinition(StepDefinition stepDefinition) {
stepDefinitions.add(stepDefinition);
Expand Down Expand Up @@ -126,6 +140,10 @@ public void addDocStringType(DocStringTypeDefinition docStringType) {
docStringTypeDefinitions.add(docStringType);
}

List<StaticHookDefinition> getBeforeAllHooks() {
return new ArrayList<>(beforeAllHooks);
}

Collection<CoreHookDefinition> getBeforeHooks() {
return new ArrayList<>(beforeHooks);
}
Expand All @@ -146,6 +164,10 @@ Collection<CoreHookDefinition> getAfterStepHooks() {
return hooks;
}

List<StaticHookDefinition> getAfterAllHooks() {
return new ArrayList<>(afterAllHooks);
}

Collection<ParameterTypeDefinition> getParameterTypeDefinitions() {
return parameterTypeDefinitions;
}
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/java/io/cucumber/core/runner/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.cucumber.core.api.TypeRegistryConfigurer;
import io.cucumber.core.backend.Backend;
import io.cucumber.core.backend.ObjectFactory;
import io.cucumber.core.backend.StaticHookDefinition;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.gherkin.Pickle;
import io.cucumber.core.gherkin.Step;
Expand Down Expand Up @@ -70,6 +71,14 @@ public void runPickle(Pickle pickle) {
}
}

public void runBeforeAllHooks(){
glue.getBeforeAllHooks().forEach(StaticHookDefinition::execute);
}

public void runAfterAllHooks(){
glue.getAfterAllHooks().forEach(StaticHookDefinition::execute);
}

private List<SnippetGenerator> createSnippetGeneratorsForPickle(StepTypeRegistry stepTypeRegistry) {
return backends.stream()
.map(Backend::getSnippet)
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/io/cucumber/core/runtime/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public void run() {
for (Feature feature : features) {
bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource()));
}
runnerSupplier.get().runBeforeAllHooks();

final List<Future<?>> executingPickles = features.stream()
.flatMap(feature -> feature.getPickles().stream())
Expand Down Expand Up @@ -119,7 +120,7 @@ public void run() {
} else if (thrown.size() > 1) {
throw new CompositeCucumberException(thrown);
}

runnerSupplier.get().runAfterAllHooks();
bus.send(new TestRunFinished(bus.getInstant()));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.cucumber.examples.java;

import io.cucumber.java.After;
import io.cucumber.java.AfterAll;
import io.cucumber.java.Before;
import io.cucumber.java.BeforeAll;
import io.cucumber.java.Scenario;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
Expand All @@ -14,6 +16,16 @@
public class RpnCalculatorSteps {
private RpnCalculator calc;

@BeforeAll
public static void enable_super_math_engine(){
// System.enableSuperMaths()
}

@AfterAll
public static void disable_super_math_engine(){
// System.disableSuperMaths()
}

@Given("a calculator I just turned on")
public void a_calculator_I_just_turned_on() {
calc = new RpnCalculator();
Expand Down
4 changes: 3 additions & 1 deletion java/src/main/java/io/cucumber/java/After.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
String value() default "";

/**
* @return the order in which this hook should run. Higher numbers are run first.
* The order in which this hook should run. Higher numbers are run first.
* The default order is 10000.
*
* @return the order in which this hook should run.
*/
int order() default 10000;
}
25 changes: 25 additions & 0 deletions java/src/main/java/io/cucumber/java/AfterAll.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.cucumber.java;

import org.apiguardian.api.API;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Executes a method before all scenarios
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@API(status = API.Status.STABLE)
public @interface AfterAll {

/**
* The order in which this hook should run. Higher numbers are run first.
* The default order is 10000.
*
* @return the order in which this hook should run.
*/
int order() default 10000;
}
4 changes: 3 additions & 1 deletion java/src/main/java/io/cucumber/java/Before.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
String value() default "";

/**
* @return the order in which this hook should run. Lower numbers are run first.
* The order in which this hook should run. Lower numbers are run first.
* The default order is 10000.
*
* @return the order in which this hook should run.
*/
int order() default 10000;
}
25 changes: 25 additions & 0 deletions java/src/main/java/io/cucumber/java/BeforeAll.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.cucumber.java;

import org.apiguardian.api.API;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Executes a method after all scenarios
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@API(status = API.Status.STABLE)
public @interface BeforeAll {

/**
* The order in which this hook should run. Lower numbers are run first.
* The default order is 10000.
*
* @return the order in which this hook should run.
*/
int order() default 10000;
}
8 changes: 7 additions & 1 deletion java/src/main/java/io/cucumber/java/GlueAdaptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ void addDefinition(Method method, Annotation annotation) {
Before before = (Before) annotation;
String tagExpression = before.value();
glue.addBeforeHook(new JavaHookDefinition(method, tagExpression, before.order(), lookup));
} else if (annotationType.equals(BeforeAll.class)) {
BeforeAll beforeAll = (BeforeAll) annotation;
glue.addBeforeAllHook(new JavaStaticHookDefinition(method, beforeAll.order(), lookup));
} else if (annotationType.equals(After.class)) {
After after = (After) annotation;
String tagExpression = after.value();
glue.addAfterHook(new JavaHookDefinition(method, tagExpression, after.order(), lookup));
} else if (annotationType.equals(AfterAll.class)) {
AfterAll afterAll = (AfterAll) annotation;
glue.addAfterAllHook(new JavaStaticHookDefinition(method, afterAll.order(), lookup));
} else if (annotationType.equals(BeforeStep.class)) {
BeforeStep beforeStep = (BeforeStep) annotation;
String tagExpression = beforeStep.value();
Expand Down Expand Up @@ -58,7 +64,7 @@ void addDefinition(Method method, Annotation annotation) {
DefaultDataTableCellTransformer cellTransformer = (DefaultDataTableCellTransformer) annotation;
String[] emptyPatterns = cellTransformer.replaceWithEmptyString();
glue.addDefaultDataTableCellTransformer(new JavaDefaultDataTableCellTransformerDefinition(method, lookup, emptyPatterns));
} else if (annotationType.equals(DocStringType.class)){
} else if (annotationType.equals(DocStringType.class)) {
DocStringType docStringType = (DocStringType) annotation;
String contentType = docStringType.contentType();
glue.addDocStringType(new JavaDocStringTypeDefinition(contentType, method, lookup));
Expand Down
52 changes: 52 additions & 0 deletions java/src/main/java/io/cucumber/java/JavaStaticHookDefinition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.cucumber.java;

import io.cucumber.core.backend.Lookup;
import io.cucumber.core.backend.StaticHookDefinition;

import java.lang.reflect.Method;

import static io.cucumber.java.InvalidMethodSignatureException.builder;
import static java.lang.reflect.Modifier.isStatic;

final class JavaStaticHookDefinition extends AbstractGlueDefinition implements StaticHookDefinition {

private final int order;
private final Lookup lookup;

JavaStaticHookDefinition(Method method, int order, Lookup lookup) {
super(requireValidMethod(method), lookup);
this.order = order;
this.lookup = lookup;
}

private static Method requireValidMethod(Method method) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 0) {
throw createInvalidSignatureException(method);
}

if (!isStatic(method.getModifiers())) {
throw createInvalidSignatureException(method);
}

return method;
}

private static InvalidMethodSignatureException createInvalidSignatureException(Method method) {
return builder(method)
.addAnnotation(BeforeAll.class)
.addAnnotation(AfterAll.class)
.addSignature("public static void before_or_after_all()")
.build();
}

@Override
public void execute() {
Invoker.invoke(this, lookup.getInstance(method.getDeclaringClass()), method);
}

@Override
public int getOrder() {
return order;
}
}
11 changes: 8 additions & 3 deletions java/src/main/java/io/cucumber/java/MethodScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.BiConsumer;

import static io.cucumber.java.InvalidMethodException.createInvalidMethodException;
import static java.lang.reflect.Modifier.isAbstract;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;

final class MethodScanner {

Expand All @@ -27,8 +29,9 @@ static void scan(Class<?> aClass, BiConsumer<Method, Annotation> consumer) {
}

private static boolean isInstantiable(Class<?> clazz) {
boolean isNonStaticInnerClass = !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null;
return Modifier.isPublic(clazz.getModifiers()) && !Modifier.isAbstract(clazz.getModifiers()) && !isNonStaticInnerClass;
return isPublic(clazz.getModifiers())
&& !isAbstract(clazz.getModifiers())
&& (isStatic(clazz.getModifiers()) || clazz.getEnclosingClass() == null);
}

private static void scan(BiConsumer<Method, Annotation> consumer, Class<?> aClass, Method method) {
Expand Down Expand Up @@ -59,7 +62,9 @@ private static void validateMethod(Class<?> glueCodeClass, Method method) {
private static boolean isHookAnnotation(Annotation annotation) {
Class<? extends Annotation> annotationClass = annotation.annotationType();
return annotationClass.equals(Before.class)
|| annotationClass.equals(BeforeAll.class)
|| annotationClass.equals(After.class)
|| annotationClass.equals(AfterAll.class)
|| annotationClass.equals(BeforeStep.class)
|| annotationClass.equals(AfterStep.class)
|| annotationClass.equals(ParameterType.class)
Expand Down
11 changes: 11 additions & 0 deletions java/src/test/java/io/cucumber/java/GlueAdaptorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.cucumber.core.backend.HookDefinition;
import io.cucumber.core.backend.Lookup;
import io.cucumber.core.backend.ParameterTypeDefinition;
import io.cucumber.core.backend.StaticHookDefinition;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.java.en.Given;
import org.hamcrest.CustomTypeSafeMatcher;
Expand Down Expand Up @@ -61,6 +62,16 @@ protected boolean matchesSafely(StepDefinition item) {
private HookDefinition beforeHook;
private DocStringTypeDefinition docStringTypeDefinition;
private final Glue container = new Glue() {
@Override
public void addBeforeAllHook(StaticHookDefinition beforeAllHook) {
//TODO
}

@Override
public void addAfterAllHook(StaticHookDefinition afterAllHook) {
//TODO
}

@Override
public void addStepDefinition(StepDefinition stepDefinition) {
GlueAdaptorTest.this.stepDefinitions.add(stepDefinition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ public final class CucumberEngineExecutionContext implements EngineExecutionCont
}

void startTestRun() {
logger.debug(() -> "Sending run test started event");
logger.debug(() -> "running before all hooks");
bus.send(new TestRunStarted(bus.getInstant()));
logger.debug(() -> "Sending run test started event");
runnerSupplier.get().runBeforeAllHooks();
}

void beforeFeature(Feature feature) {
Expand All @@ -83,6 +85,8 @@ void runTestCase(Pickle pickle) {
}

void finishTestRun() {
logger.debug(() -> "running after all hooks");
runnerSupplier.get().runAfterAllHooks();
logger.debug(() -> "Sending test run finished event");
bus.send(new TestRunFinished(bus.getInstant()));
}
Expand Down
Loading