Skip to content

Commit 2f4e9da

Browse files
committed
[Java] Add BeforeAll and AfterAll hooks
Work in progress to fix #515. TODO: - [ ] How to deal with failure? - [ ] Test with JUnit4 - [ ] Test with JUnit5 - [ ] Test with TestNG - [ ] Test with CLI - [ ] Invoke around semantics? - [ ] How to report execution results? - [ ] TeamCity Plugin - [ ] Pretty formatter - [ ] Messages/Events
1 parent 218dcae commit 2f4e9da

File tree

17 files changed

+205
-9
lines changed

17 files changed

+205
-9
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
@API(status = API.Status.STABLE)
66
public interface Glue {
77

8+
void addBeforeAllHook(StaticHookDefinition beforeAllHook);
9+
10+
void addAfterAllHook(StaticHookDefinition afterAllHook);
11+
812
void addStepDefinition(StepDefinition stepDefinition);
913

1014
void addBeforeHook(HookDefinition beforeHook);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.cucumber.core.backend;
2+
3+
import org.apiguardian.api.API;
4+
5+
@API(status = API.Status.STABLE)
6+
public interface StaticHookDefinition extends Located {
7+
8+
void execute();
9+
10+
int getOrder();
11+
}

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

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.cucumber.core.backend.HookDefinition;
1010
import io.cucumber.core.backend.ParameterTypeDefinition;
1111
import io.cucumber.core.backend.ScenarioScoped;
12+
import io.cucumber.core.backend.StaticHookDefinition;
1213
import io.cucumber.core.backend.StepDefinition;
1314
import io.cucumber.core.eventbus.EventBus;
1415
import io.cucumber.core.gherkin.Step;
@@ -45,11 +46,13 @@ final class CachingGlue implements Glue {
4546
private final List<DefaultDataTableCellTransformerDefinition> defaultDataTableCellTransformers = new ArrayList<>();
4647
private final List<DocStringTypeDefinition> docStringTypeDefinitions = new ArrayList<>();
4748

49+
private final List<StaticHookDefinition> beforeAllHooks = new ArrayList<>();
4850
private final List<CoreHookDefinition> beforeHooks = new ArrayList<>();
4951
private final List<CoreHookDefinition> beforeStepHooks = new ArrayList<>();
5052
private final List<StepDefinition> stepDefinitions = new ArrayList<>();
5153
private final List<CoreHookDefinition> afterStepHooks = new ArrayList<>();
5254
private final List<CoreHookDefinition> afterHooks = new ArrayList<>();
55+
private final List<StaticHookDefinition> afterAllHooks = new ArrayList<>();
5356

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

73+
@Override
74+
public void addBeforeAllHook(StaticHookDefinition beforeAllHook) {
75+
beforeAllHooks.add(beforeAllHook);
76+
77+
}
78+
79+
@Override
80+
public void addAfterAllHook(StaticHookDefinition afterAllHook) {
81+
afterAllHooks.add(afterAllHook);
82+
}
83+
7084
@Override
7185
public void addStepDefinition(StepDefinition stepDefinition) {
7286
stepDefinitions.add(stepDefinition);
@@ -126,6 +140,10 @@ public void addDocStringType(DocStringTypeDefinition docStringType) {
126140
docStringTypeDefinitions.add(docStringType);
127141
}
128142

143+
List<StaticHookDefinition> getBeforeAllHooks() {
144+
return new ArrayList<>(beforeAllHooks);
145+
}
146+
129147
Collection<CoreHookDefinition> getBeforeHooks() {
130148
return new ArrayList<>(beforeHooks);
131149
}
@@ -146,6 +164,10 @@ Collection<CoreHookDefinition> getAfterStepHooks() {
146164
return hooks;
147165
}
148166

167+
List<StaticHookDefinition> getAfterAllHooks() {
168+
return new ArrayList<>(afterAllHooks);
169+
}
170+
149171
Collection<ParameterTypeDefinition> getParameterTypeDefinitions() {
150172
return parameterTypeDefinitions;
151173
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.cucumber.core.api.TypeRegistryConfigurer;
44
import io.cucumber.core.backend.Backend;
55
import io.cucumber.core.backend.ObjectFactory;
6+
import io.cucumber.core.backend.StaticHookDefinition;
67
import io.cucumber.core.eventbus.EventBus;
78
import io.cucumber.core.gherkin.Pickle;
89
import io.cucumber.core.gherkin.Step;
@@ -70,6 +71,14 @@ public void runPickle(Pickle pickle) {
7071
}
7172
}
7273

74+
public void runBeforeAllHooks(){
75+
glue.getBeforeAllHooks().forEach(StaticHookDefinition::execute);
76+
}
77+
78+
public void runAfterAllHooks(){
79+
glue.getAfterAllHooks().forEach(StaticHookDefinition::execute);
80+
}
81+
7382
private List<SnippetGenerator> createSnippetGeneratorsForPickle(StepTypeRegistry stepTypeRegistry) {
7483
return backends.stream()
7584
.map(Backend::getSnippet)

core/src/main/java/io/cucumber/core/runtime/Runtime.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public void run() {
9090
for (Feature feature : features) {
9191
bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource()));
9292
}
93+
runnerSupplier.get().runBeforeAllHooks();
9394

9495
final List<Future<?>> executingPickles = features.stream()
9596
.flatMap(feature -> feature.getPickles().stream())
@@ -119,7 +120,7 @@ public void run() {
119120
} else if (thrown.size() > 1) {
120121
throw new CompositeCucumberException(thrown);
121122
}
122-
123+
runnerSupplier.get().runAfterAllHooks();
123124
bus.send(new TestRunFinished(bus.getInstant()));
124125
}
125126

examples/java-calculator/src/test/java/io/cucumber/examples/java/RpnCalculatorSteps.java

+12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.cucumber.examples.java;
22

33
import io.cucumber.java.After;
4+
import io.cucumber.java.AfterAll;
45
import io.cucumber.java.Before;
6+
import io.cucumber.java.BeforeAll;
57
import io.cucumber.java.Scenario;
68
import io.cucumber.java.en.Given;
79
import io.cucumber.java.en.Then;
@@ -14,6 +16,16 @@
1416
public class RpnCalculatorSteps {
1517
private RpnCalculator calc;
1618

19+
@BeforeAll
20+
public static void enable_super_math_engine(){
21+
// System.enableSuperMaths()
22+
}
23+
24+
@AfterAll
25+
public static void disable_super_math_engine(){
26+
// System.disableSuperMaths()
27+
}
28+
1729
@Given("a calculator I just turned on")
1830
public void a_calculator_I_just_turned_on() {
1931
calc = new RpnCalculator();

java/src/main/java/io/cucumber/java/After.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
String value() default "";
2424

2525
/**
26-
* @return the order in which this hook should run. Higher numbers are run first.
26+
* The order in which this hook should run. Higher numbers are run first.
2727
* The default order is 10000.
28+
*
29+
* @return the order in which this hook should run.
2830
*/
2931
int order() default 10000;
3032
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.cucumber.java;
2+
3+
import org.apiguardian.api.API;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Executes a method before all scenarios
12+
*/
13+
@Retention(RetentionPolicy.RUNTIME)
14+
@Target(ElementType.METHOD)
15+
@API(status = API.Status.STABLE)
16+
public @interface AfterAll {
17+
18+
/**
19+
* The order in which this hook should run. Higher numbers are run first.
20+
* The default order is 10000.
21+
*
22+
* @return the order in which this hook should run.
23+
*/
24+
int order() default 10000;
25+
}

java/src/main/java/io/cucumber/java/Before.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
String value() default "";
2424

2525
/**
26-
* @return the order in which this hook should run. Lower numbers are run first.
26+
* The order in which this hook should run. Lower numbers are run first.
2727
* The default order is 10000.
28+
*
29+
* @return the order in which this hook should run.
2830
*/
2931
int order() default 10000;
3032
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.cucumber.java;
2+
3+
import org.apiguardian.api.API;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Executes a method after all scenarios
12+
*/
13+
@Retention(RetentionPolicy.RUNTIME)
14+
@Target(ElementType.METHOD)
15+
@API(status = API.Status.STABLE)
16+
public @interface BeforeAll {
17+
18+
/**
19+
* The order in which this hook should run. Lower numbers are run first.
20+
* The default order is 10000.
21+
*
22+
* @return the order in which this hook should run.
23+
*/
24+
int order() default 10000;
25+
}

java/src/main/java/io/cucumber/java/GlueAdaptor.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,16 @@ void addDefinition(Method method, Annotation annotation) {
2525
Before before = (Before) annotation;
2626
String tagExpression = before.value();
2727
glue.addBeforeHook(new JavaHookDefinition(method, tagExpression, before.order(), lookup));
28+
} else if (annotationType.equals(BeforeAll.class)) {
29+
BeforeAll beforeAll = (BeforeAll) annotation;
30+
glue.addBeforeAllHook(new JavaStaticHookDefinition(method, beforeAll.order(), lookup));
2831
} else if (annotationType.equals(After.class)) {
2932
After after = (After) annotation;
3033
String tagExpression = after.value();
3134
glue.addAfterHook(new JavaHookDefinition(method, tagExpression, after.order(), lookup));
35+
} else if (annotationType.equals(AfterAll.class)) {
36+
AfterAll afterAll = (AfterAll) annotation;
37+
glue.addAfterAllHook(new JavaStaticHookDefinition(method, afterAll.order(), lookup));
3238
} else if (annotationType.equals(BeforeStep.class)) {
3339
BeforeStep beforeStep = (BeforeStep) annotation;
3440
String tagExpression = beforeStep.value();
@@ -58,7 +64,7 @@ void addDefinition(Method method, Annotation annotation) {
5864
DefaultDataTableCellTransformer cellTransformer = (DefaultDataTableCellTransformer) annotation;
5965
String[] emptyPatterns = cellTransformer.replaceWithEmptyString();
6066
glue.addDefaultDataTableCellTransformer(new JavaDefaultDataTableCellTransformerDefinition(method, lookup, emptyPatterns));
61-
} else if (annotationType.equals(DocStringType.class)){
67+
} else if (annotationType.equals(DocStringType.class)) {
6268
DocStringType docStringType = (DocStringType) annotation;
6369
String contentType = docStringType.contentType();
6470
glue.addDocStringType(new JavaDocStringTypeDefinition(contentType, method, lookup));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.cucumber.java;
2+
3+
import io.cucumber.core.backend.Lookup;
4+
import io.cucumber.core.backend.StaticHookDefinition;
5+
6+
import java.lang.reflect.Method;
7+
8+
import static io.cucumber.java.InvalidMethodSignatureException.builder;
9+
import static java.lang.reflect.Modifier.isStatic;
10+
11+
final class JavaStaticHookDefinition extends AbstractGlueDefinition implements StaticHookDefinition {
12+
13+
private final int order;
14+
private final Lookup lookup;
15+
16+
JavaStaticHookDefinition(Method method, int order, Lookup lookup) {
17+
super(requireValidMethod(method), lookup);
18+
this.order = order;
19+
this.lookup = lookup;
20+
}
21+
22+
private static Method requireValidMethod(Method method) {
23+
Class<?>[] parameterTypes = method.getParameterTypes();
24+
if (parameterTypes.length != 0) {
25+
throw createInvalidSignatureException(method);
26+
}
27+
28+
if (!isStatic(method.getModifiers())) {
29+
throw createInvalidSignatureException(method);
30+
}
31+
32+
return method;
33+
}
34+
35+
private static InvalidMethodSignatureException createInvalidSignatureException(Method method) {
36+
return builder(method)
37+
.addAnnotation(BeforeAll.class)
38+
.addAnnotation(AfterAll.class)
39+
.addSignature("public static void before_or_after_all()")
40+
.build();
41+
}
42+
43+
@Override
44+
public void execute() {
45+
Invoker.invoke(this, lookup.getInstance(method.getDeclaringClass()), method);
46+
}
47+
48+
@Override
49+
public int getOrder() {
50+
return order;
51+
}
52+
}

java/src/main/java/io/cucumber/java/MethodScanner.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import java.lang.annotation.Annotation;
44
import java.lang.reflect.Method;
5-
import java.lang.reflect.Modifier;
65
import java.util.function.BiConsumer;
76

87
import static io.cucumber.java.InvalidMethodException.createInvalidMethodException;
8+
import static java.lang.reflect.Modifier.isAbstract;
9+
import static java.lang.reflect.Modifier.isPublic;
10+
import static java.lang.reflect.Modifier.isStatic;
911

1012
final class MethodScanner {
1113

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

2931
private static boolean isInstantiable(Class<?> clazz) {
30-
boolean isNonStaticInnerClass = !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null;
31-
return Modifier.isPublic(clazz.getModifiers()) && !Modifier.isAbstract(clazz.getModifiers()) && !isNonStaticInnerClass;
32+
return isPublic(clazz.getModifiers())
33+
&& !isAbstract(clazz.getModifiers())
34+
&& (isStatic(clazz.getModifiers()) || clazz.getEnclosingClass() == null);
3235
}
3336

3437
private static void scan(BiConsumer<Method, Annotation> consumer, Class<?> aClass, Method method) {
@@ -59,7 +62,9 @@ private static void validateMethod(Class<?> glueCodeClass, Method method) {
5962
private static boolean isHookAnnotation(Annotation annotation) {
6063
Class<? extends Annotation> annotationClass = annotation.annotationType();
6164
return annotationClass.equals(Before.class)
65+
|| annotationClass.equals(BeforeAll.class)
6266
|| annotationClass.equals(After.class)
67+
|| annotationClass.equals(AfterAll.class)
6368
|| annotationClass.equals(BeforeStep.class)
6469
|| annotationClass.equals(AfterStep.class)
6570
|| annotationClass.equals(ParameterType.class)

java/src/test/java/io/cucumber/java/GlueAdaptorTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.cucumber.core.backend.HookDefinition;
1010
import io.cucumber.core.backend.Lookup;
1111
import io.cucumber.core.backend.ParameterTypeDefinition;
12+
import io.cucumber.core.backend.StaticHookDefinition;
1213
import io.cucumber.core.backend.StepDefinition;
1314
import io.cucumber.java.en.Given;
1415
import org.hamcrest.CustomTypeSafeMatcher;
@@ -61,6 +62,16 @@ protected boolean matchesSafely(StepDefinition item) {
6162
private HookDefinition beforeHook;
6263
private DocStringTypeDefinition docStringTypeDefinition;
6364
private final Glue container = new Glue() {
65+
@Override
66+
public void addBeforeAllHook(StaticHookDefinition beforeAllHook) {
67+
//TODO
68+
}
69+
70+
@Override
71+
public void addAfterAllHook(StaticHookDefinition afterAllHook) {
72+
//TODO
73+
}
74+
6475
@Override
6576
public void addStepDefinition(StepDefinition stepDefinition) {
6677
GlueAdaptorTest.this.stepDefinitions.add(stepDefinition);

junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ public final class CucumberEngineExecutionContext implements EngineExecutionCont
6262
}
6363

6464
void startTestRun() {
65-
logger.debug(() -> "Sending run test started event");
65+
logger.debug(() -> "running before all hooks");
6666
bus.send(new TestRunStarted(bus.getInstant()));
67+
logger.debug(() -> "Sending run test started event");
68+
runnerSupplier.get().runBeforeAllHooks();
6769
}
6870

6971
void beforeFeature(Feature feature) {
@@ -83,6 +85,8 @@ void runTestCase(Pickle pickle) {
8385
}
8486

8587
void finishTestRun() {
88+
logger.debug(() -> "running after all hooks");
89+
runnerSupplier.get().runAfterAllHooks();
8690
logger.debug(() -> "Sending test run finished event");
8791
bus.send(new TestRunFinished(bus.getInstant()));
8892
}

0 commit comments

Comments
 (0)