Skip to content

Commit 3bc80b9

Browse files
authored
[Java] Add BeforeAll and AfterAll hooks (#1876)
`BeforeAll` and `AfterAll` hooks are executed before all scenarios are executed and after all scenarios have been executed. A hook is declared by annotating a method. This methods must be static and do not take any arguments. Hooks are global, all hooks declared in any step definition class will be executed. The order in which hooks are executed is not defined. An explicit order can be provided by using the `order` property in the annotation. ```java package io.cucumber.example; import io.cucumber.java.AfterAll; import io.cucumber.java.BeforeAll; public class StepDefinitions { @BeforeAll public static void beforeAll() { // Runs before all scenarios } @afterall public static void afterAll() { // Runs after all scenarios } } ``` Notes: 1. When used in combination with Junit 5, Maven Surefire, and/or Failsafe use version `3.0.0-M5` or later. 2. When used in combination with Junit 5 and InteliJ IDEA failures in before all and after all hooks do not fail a test run. Fixes: #515
1 parent 71bb1cc commit 3bc80b9

File tree

53 files changed

+1146
-244
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1146
-244
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
## [Unreleased] (In Git)
99

1010
### Added
11+
* [Java] Added `BeforeAll` and `AfterAll` hooks ([cucumber/#1876](https://github.com/cucumber/cucumber/pull/1876) M.P. Korstanje)
1112

1213
### Changed
1314
* [Core] Updated Cucumber Expressions to v11 ([cucumber/#711](https://github.com/cucumber/cucumber/pull/771) M.P. Korstanje)

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.EXPERIMENTAL)
6+
public interface StaticHookDefinition extends Located {
7+
8+
void execute();
9+
10+
int getOrder();
11+
}

core/src/main/java/io/cucumber/core/cli/Main.java

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.cucumber.core.cli;
22

3-
import io.cucumber.core.logging.Logger;
4-
import io.cucumber.core.logging.LoggerFactory;
53
import io.cucumber.core.options.CommandlineOptionsParser;
64
import io.cucumber.core.options.Constants;
75
import io.cucumber.core.options.CucumberProperties;
@@ -28,8 +26,6 @@
2826
@API(status = API.Status.STABLE)
2927
public class Main {
3028

31-
private static final Logger log = LoggerFactory.getLogger(Main.class);
32-
3329
public static void main(String... argv) {
3430
byte exitStatus = run(argv, Thread.currentThread().getContextClassLoader());
3531
System.exit(exitStatus);
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
package io.cucumber.core.exception;
22

3-
import java.util.Collections;
43
import java.util.List;
5-
import java.util.stream.Collectors;
64

75
public final class CompositeCucumberException extends CucumberException {
86

9-
private final List<Throwable> causes;
10-
117
public CompositeCucumberException(List<Throwable> causes) {
12-
super(String.format("There were %d exceptions:", causes.size()));
13-
this.causes = causes;
14-
}
15-
16-
public List<Throwable> getCauses() {
17-
return Collections.unmodifiableList(this.causes);
18-
}
19-
20-
public String getMessage() {
21-
return super.getMessage() + this.causes.stream()
22-
.map(e -> String.format(" %s(%s)", e.getClass().getName(), e.getMessage()))
23-
.collect(Collectors.joining("\n", "\n", ""));
8+
super(String.format("There were %d exceptions. The details are in the stacktrace below.", causes.size()));
9+
causes.forEach(this::addSuppressed);
2410
}
2511

2612
}

core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,9 @@ private Map<String, Object> createDummyFeatureForFailure(TestRunFinished event)
354354

355355
scenario.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant()));
356356
scenario.put("line", 2);
357-
scenario.put("name", "Could not execute Cucumber");
357+
scenario.put("name", "Failure while executing Cucumber");
358358
scenario.put("description", "");
359-
scenario.put("id", "failure;could-not-execute-cucumber");
359+
scenario.put("id", "failure;failure-while-executing-cucumber");
360360
scenario.put("type", "scenario");
361361
scenario.put("keyword", "Scenario");
362362

@@ -372,18 +372,18 @@ private Map<String, Object> createDummyFeatureForFailure(TestRunFinished event)
372372
whenResult.put("status", "passed");
373373
}
374374
when.put("line", 3);
375-
when.put("name", "Cucumber could not execute");
375+
when.put("name", "Cucumber failed while executing");
376376
Map<String, Object> whenMatch = new LinkedHashMap<>();
377377
when.put("match", whenMatch);
378378
whenMatch.put("arguments", new ArrayList<>());
379-
whenMatch.put("location", "io.cucumber.core.Failure.cucumber_could_not_execute()");
379+
whenMatch.put("location", "io.cucumber.core.Failure.failure_while_executing_cucumber()");
380380
when.put("keyword", "When ");
381381

382382
{
383383
Map<String, Object> thenResult = new LinkedHashMap<>();
384384
then.put("result", thenResult);
385385
thenResult.put("duration", 0);
386-
thenResult.put("error_message", exception.getMessage());
386+
thenResult.put("error_message", printStackTrace(exception));
387387
thenResult.put("status", "failed");
388388
}
389389
then.put("line", 4);

core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ private void handleEmbed(EmbedEvent event) {
8484
}
8585

8686
private void handleTestRunFinished(TestRunFinished event) {
87+
printError(event);
8788
out.close();
8889
}
8990

@@ -132,11 +133,21 @@ private void printStep(TestStepFinished event) {
132133

133134
private void printError(TestStepFinished event) {
134135
Result result = event.getResult();
136+
printError(result);
137+
}
138+
139+
private void printError(TestRunFinished event) {
140+
Result result = event.getResult();
141+
printError(result);
142+
}
143+
144+
private void printError(Result result) {
135145
Throwable error = result.getError();
136146
if (error != null) {
137147
String name = result.getStatus().name().toLowerCase(ROOT);
148+
Format format = formats.get(name);
138149
String text = printStackTrace(error);
139-
out.println(" " + formats.get(name).text(text));
150+
out.println(" " + format.text(text));
140151
}
141152
}
142153

core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java

-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import io.cucumber.plugin.event.Status;
88
import io.cucumber.plugin.event.TestRunFinished;
99
import io.cucumber.plugin.event.TestStepFinished;
10-
import io.cucumber.plugin.event.WriteEvent;
1110

1211
import java.io.OutputStream;
1312
import java.util.HashMap;
@@ -51,7 +50,6 @@ public void setMonochrome(boolean monochrome) {
5150
@Override
5251
public void setEventPublisher(EventPublisher publisher) {
5352
publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished);
54-
publisher.registerHandlerFor(WriteEvent.class, this::handleWrite);
5553
publisher.registerHandlerFor(TestRunFinished.class, event -> handleTestRunFinished());
5654
}
5755

@@ -67,10 +65,6 @@ private void handleTestStepFinished(TestStepFinished event) {
6765
}
6866
}
6967

70-
private void handleWrite(WriteEvent event) {
71-
out.append(event.getText());
72-
}
73-
7468
private void handleTestRunFinished() {
7569
out.println();
7670
out.close();

core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java

+23-10
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@
2424
import io.cucumber.plugin.event.TestStepStarted;
2525
import io.cucumber.plugin.event.WriteEvent;
2626

27-
import java.io.ByteArrayOutputStream;
2827
import java.io.PrintStream;
2928
import java.net.URI;
30-
import java.nio.charset.StandardCharsets;
3129
import java.time.ZoneOffset;
3230
import java.time.ZonedDateTime;
3331
import java.time.format.DateTimeFormatter;
@@ -45,6 +43,7 @@
4543
import java.util.regex.Pattern;
4644
import java.util.stream.Collectors;
4745

46+
import static io.cucumber.core.exception.ExceptionUtils.printStackTrace;
4847
import static java.util.Collections.emptyList;
4948
import static java.util.stream.Collectors.joining;
5049

@@ -81,6 +80,13 @@ public class TeamCityPlugin implements EventListener {
8180
private static final String TEMPLATE_TEST_IGNORED = TEAMCITY_PREFIX
8281
+ "[testIgnored timestamp = '%s' duration = '%s' message = '%s' name = '%s']";
8382

83+
private static final String TEMPLATE_BEFORE_ALL_AFTER_ALL_STARTED = TEAMCITY_PREFIX
84+
+ "[testStarted timestamp = '%s' name = '%s']";
85+
private static final String TEMPLATE_BEFORE_ALL_AFTER_ALL_FAILED = TEAMCITY_PREFIX
86+
+ "[testFailed timestamp = '%s' message = '%s' details = '%s' name = '%s']";
87+
private static final String TEMPLATE_BEFORE_ALL_AFTER_ALL_FINISHED = TEAMCITY_PREFIX
88+
+ "[testFinished timestamp = '%s' name = '%s']";
89+
8490
private static final String TEMPLATE_PROGRESS_COUNTING_STARTED = TEAMCITY_PREFIX
8591
+ "[customProgressStatus testsCategory = 'Scenarios' count = '0' timestamp = '%s']";
8692
private static final String TEMPLATE_PROGRESS_COUNTING_FINISHED = TEAMCITY_PREFIX
@@ -274,7 +280,7 @@ private void printTestStepFinished(TestStepFinished event) {
274280
}
275281
case AMBIGUOUS:
276282
case FAILED: {
277-
String details = extractStackTrace(error);
283+
String details = printStackTrace(error);
278284
print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name);
279285
break;
280286
}
@@ -284,13 +290,6 @@ private void printTestStepFinished(TestStepFinished event) {
284290
print(TEMPLATE_TEST_FINISHED, timeStamp, duration, name);
285291
}
286292

287-
private String extractStackTrace(Throwable error) {
288-
ByteArrayOutputStream s = new ByteArrayOutputStream();
289-
PrintStream printStream = new PrintStream(s);
290-
error.printStackTrace(printStream);
291-
return new String(s.toByteArray(), StandardCharsets.UTF_8);
292-
}
293-
294293
private String extractName(TestStep step) {
295294
if (step instanceof PickleStepTestStep) {
296295
PickleStepTestStep pickleStepTestStep = (PickleStepTestStep) step;
@@ -364,9 +363,23 @@ private void printTestRunFinished(TestRunFinished event) {
364363
poppedNodes(emptyStack).forEach(node -> finishNode(timestamp, node));
365364
currentStack = emptyStack;
366365

366+
printBeforeAfterAllResult(event, timestamp);
367367
print(TEMPLATE_TEST_RUN_FINISHED, timestamp);
368368
}
369369

370+
private void printBeforeAfterAllResult(TestRunFinished event, String timestamp) {
371+
Throwable error = event.getResult().getError();
372+
if (error == null) {
373+
return;
374+
}
375+
// Use dummy test to display before all after all failures
376+
String name = "Before All/After All";
377+
print(TEMPLATE_BEFORE_ALL_AFTER_ALL_STARTED, timestamp, name);
378+
String details = printStackTrace(error);
379+
print(TEMPLATE_BEFORE_ALL_AFTER_ALL_FAILED, timestamp, "Before All/ After All failed", details, name);
380+
print(TEMPLATE_BEFORE_ALL_AFTER_ALL_FINISHED, timestamp, name);
381+
}
382+
370383
private void handleSnippetSuggested(SnippetsSuggestedEvent event) {
371384
suggestions.add(event);
372385
}

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

+34-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.cucumber.core.backend.ParameterTypeDefinition;
1212
import io.cucumber.core.backend.ScenarioScoped;
1313
import io.cucumber.core.backend.StackTraceElementReference;
14+
import io.cucumber.core.backend.StaticHookDefinition;
1415
import io.cucumber.core.backend.StepDefinition;
1516
import io.cucumber.core.eventbus.EventBus;
1617
import io.cucumber.core.gherkin.Step;
@@ -48,21 +49,27 @@
4849

4950
final class CachingGlue implements Glue {
5051

51-
private static final Comparator<CoreHookDefinition> ASCENDING = Comparator
52+
private static final Comparator<CoreHookDefinition> HOOK_ORDER_ASCENDING = Comparator
5253
.comparingInt(CoreHookDefinition::getOrder)
5354
.thenComparing(ScenarioScoped.class::isInstance);
55+
56+
private static final Comparator<StaticHookDefinition> STATIC_HOOK_ORDER_ASCENDING = Comparator
57+
.comparingInt(StaticHookDefinition::getOrder);
58+
5459
private final List<ParameterTypeDefinition> parameterTypeDefinitions = new ArrayList<>();
5560
private final List<DataTableTypeDefinition> dataTableTypeDefinitions = new ArrayList<>();
5661
private final List<DefaultParameterTransformerDefinition> defaultParameterTransformers = new ArrayList<>();
5762
private final List<CoreDefaultDataTableEntryTransformerDefinition> defaultDataTableEntryTransformers = new ArrayList<>();
5863
private final List<DefaultDataTableCellTransformerDefinition> defaultDataTableCellTransformers = new ArrayList<>();
5964
private final List<DocStringTypeDefinition> docStringTypeDefinitions = new ArrayList<>();
6065

66+
private final List<StaticHookDefinition> beforeAllHooks = new ArrayList<>();
6167
private final List<CoreHookDefinition> beforeHooks = new ArrayList<>();
6268
private final List<CoreHookDefinition> beforeStepHooks = new ArrayList<>();
6369
private final List<StepDefinition> stepDefinitions = new ArrayList<>();
6470
private final List<CoreHookDefinition> afterStepHooks = new ArrayList<>();
6571
private final List<CoreHookDefinition> afterHooks = new ArrayList<>();
72+
private final List<StaticHookDefinition> afterAllHooks = new ArrayList<>();
6673

6774
/*
6875
* Storing the pattern that matches the step text allows us to cache the
@@ -79,6 +86,18 @@ final class CachingGlue implements Glue {
7986
this.bus = bus;
8087
}
8188

89+
@Override
90+
public void addBeforeAllHook(StaticHookDefinition beforeAllHook) {
91+
beforeAllHooks.add(beforeAllHook);
92+
beforeAllHooks.sort(STATIC_HOOK_ORDER_ASCENDING);
93+
}
94+
95+
@Override
96+
public void addAfterAllHook(StaticHookDefinition afterAllHook) {
97+
afterAllHooks.add(afterAllHook);
98+
afterAllHooks.sort(STATIC_HOOK_ORDER_ASCENDING);
99+
}
100+
82101
@Override
83102
public void addStepDefinition(StepDefinition stepDefinition) {
84103
stepDefinitions.add(stepDefinition);
@@ -87,25 +106,25 @@ public void addStepDefinition(StepDefinition stepDefinition) {
87106
@Override
88107
public void addBeforeHook(HookDefinition hookDefinition) {
89108
beforeHooks.add(CoreHookDefinition.create(hookDefinition));
90-
beforeHooks.sort(ASCENDING);
109+
beforeHooks.sort(HOOK_ORDER_ASCENDING);
91110
}
92111

93112
@Override
94113
public void addAfterHook(HookDefinition hookDefinition) {
95114
afterHooks.add(CoreHookDefinition.create(hookDefinition));
96-
afterHooks.sort(ASCENDING);
115+
afterHooks.sort(HOOK_ORDER_ASCENDING);
97116
}
98117

99118
@Override
100119
public void addBeforeStepHook(HookDefinition hookDefinition) {
101120
beforeStepHooks.add(CoreHookDefinition.create(hookDefinition));
102-
beforeStepHooks.sort(ASCENDING);
121+
beforeStepHooks.sort(HOOK_ORDER_ASCENDING);
103122
}
104123

105124
@Override
106125
public void addAfterStepHook(HookDefinition hookDefinition) {
107126
afterStepHooks.add(CoreHookDefinition.create(hookDefinition));
108-
afterStepHooks.sort(ASCENDING);
127+
afterStepHooks.sort(HOOK_ORDER_ASCENDING);
109128
}
110129

111130
@Override
@@ -143,6 +162,10 @@ public void addDocStringType(DocStringTypeDefinition docStringType) {
143162
docStringTypeDefinitions.add(docStringType);
144163
}
145164

165+
List<StaticHookDefinition> getBeforeAllHooks() {
166+
return new ArrayList<>(beforeAllHooks);
167+
}
168+
146169
Collection<CoreHookDefinition> getBeforeHooks() {
147170
return new ArrayList<>(beforeHooks);
148171
}
@@ -163,6 +186,12 @@ Collection<CoreHookDefinition> getAfterStepHooks() {
163186
return hooks;
164187
}
165188

189+
List<StaticHookDefinition> getAfterAllHooks() {
190+
ArrayList<StaticHookDefinition> hooks = new ArrayList<>(afterAllHooks);
191+
Collections.reverse(hooks);
192+
return hooks;
193+
}
194+
166195
Collection<ParameterTypeDefinition> getParameterTypeDefinitions() {
167196
return parameterTypeDefinitions;
168197
}

0 commit comments

Comments
 (0)