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 all commits
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] (In Git)

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

### Changed
* [Core] Updated Cucumber Expressions to v11 ([cucumber/#711](https://github.com/cucumber/cucumber/pull/771) M.P. Korstanje)
Expand Down
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.EXPERIMENTAL)
public interface StaticHookDefinition extends Located {

void execute();

int getOrder();
}
4 changes: 0 additions & 4 deletions core/src/main/java/io/cucumber/core/cli/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.cucumber.core.cli;

import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
import io.cucumber.core.options.CommandlineOptionsParser;
import io.cucumber.core.options.Constants;
import io.cucumber.core.options.CucumberProperties;
Expand All @@ -28,8 +26,6 @@
@API(status = API.Status.STABLE)
public class Main {

private static final Logger log = LoggerFactory.getLogger(Main.class);

public static void main(String... argv) {
byte exitStatus = run(argv, Thread.currentThread().getContextClassLoader());
System.exit(exitStatus);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
package io.cucumber.core.exception;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public final class CompositeCucumberException extends CucumberException {

private final List<Throwable> causes;

public CompositeCucumberException(List<Throwable> causes) {
super(String.format("There were %d exceptions:", causes.size()));
this.causes = causes;
}

public List<Throwable> getCauses() {
return Collections.unmodifiableList(this.causes);
}

public String getMessage() {
return super.getMessage() + this.causes.stream()
.map(e -> String.format(" %s(%s)", e.getClass().getName(), e.getMessage()))
.collect(Collectors.joining("\n", "\n", ""));
super(String.format("There were %d exceptions. The details are in the stacktrace below.", causes.size()));
causes.forEach(this::addSuppressed);
}

}
10 changes: 5 additions & 5 deletions core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,9 @@ private Map<String, Object> createDummyFeatureForFailure(TestRunFinished event)

scenario.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant()));
scenario.put("line", 2);
scenario.put("name", "Could not execute Cucumber");
scenario.put("name", "Failure while executing Cucumber");
scenario.put("description", "");
scenario.put("id", "failure;could-not-execute-cucumber");
scenario.put("id", "failure;failure-while-executing-cucumber");
scenario.put("type", "scenario");
scenario.put("keyword", "Scenario");

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

{
Map<String, Object> thenResult = new LinkedHashMap<>();
then.put("result", thenResult);
thenResult.put("duration", 0);
thenResult.put("error_message", exception.getMessage());
thenResult.put("error_message", printStackTrace(exception));
thenResult.put("status", "failed");
}
then.put("line", 4);
Expand Down
13 changes: 12 additions & 1 deletion core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ private void handleEmbed(EmbedEvent event) {
}

private void handleTestRunFinished(TestRunFinished event) {
printError(event);
out.close();
}

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

private void printError(TestStepFinished event) {
Result result = event.getResult();
printError(result);
}

private void printError(TestRunFinished event) {
Result result = event.getResult();
printError(result);
}

private void printError(Result result) {
Throwable error = result.getError();
if (error != null) {
String name = result.getStatus().name().toLowerCase(ROOT);
Format format = formats.get(name);
String text = printStackTrace(error);
out.println(" " + formats.get(name).text(text));
out.println(" " + format.text(text));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.plugin.event.WriteEvent;

import java.io.OutputStream;
import java.util.HashMap;
Expand Down Expand Up @@ -51,7 +50,6 @@ public void setMonochrome(boolean monochrome) {
@Override
public void setEventPublisher(EventPublisher publisher) {
publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished);
publisher.registerHandlerFor(WriteEvent.class, this::handleWrite);
publisher.registerHandlerFor(TestRunFinished.class, event -> handleTestRunFinished());
}

Expand All @@ -67,10 +65,6 @@ private void handleTestStepFinished(TestStepFinished event) {
}
}

private void handleWrite(WriteEvent event) {
out.append(event.getText());
}

private void handleTestRunFinished() {
out.println();
out.close();
Expand Down
33 changes: 23 additions & 10 deletions core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@
import io.cucumber.plugin.event.TestStepStarted;
import io.cucumber.plugin.event.WriteEvent;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
Expand All @@ -45,6 +43,7 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static io.cucumber.core.exception.ExceptionUtils.printStackTrace;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;

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

private static final String TEMPLATE_BEFORE_ALL_AFTER_ALL_STARTED = TEAMCITY_PREFIX
+ "[testStarted timestamp = '%s' name = '%s']";
private static final String TEMPLATE_BEFORE_ALL_AFTER_ALL_FAILED = TEAMCITY_PREFIX
+ "[testFailed timestamp = '%s' message = '%s' details = '%s' name = '%s']";
private static final String TEMPLATE_BEFORE_ALL_AFTER_ALL_FINISHED = TEAMCITY_PREFIX
+ "[testFinished timestamp = '%s' name = '%s']";

private static final String TEMPLATE_PROGRESS_COUNTING_STARTED = TEAMCITY_PREFIX
+ "[customProgressStatus testsCategory = 'Scenarios' count = '0' timestamp = '%s']";
private static final String TEMPLATE_PROGRESS_COUNTING_FINISHED = TEAMCITY_PREFIX
Expand Down Expand Up @@ -274,7 +280,7 @@ private void printTestStepFinished(TestStepFinished event) {
}
case AMBIGUOUS:
case FAILED: {
String details = extractStackTrace(error);
String details = printStackTrace(error);
print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name);
break;
}
Expand All @@ -284,13 +290,6 @@ private void printTestStepFinished(TestStepFinished event) {
print(TEMPLATE_TEST_FINISHED, timeStamp, duration, name);
}

private String extractStackTrace(Throwable error) {
ByteArrayOutputStream s = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(s);
error.printStackTrace(printStream);
return new String(s.toByteArray(), StandardCharsets.UTF_8);
}

private String extractName(TestStep step) {
if (step instanceof PickleStepTestStep) {
PickleStepTestStep pickleStepTestStep = (PickleStepTestStep) step;
Expand Down Expand Up @@ -364,9 +363,23 @@ private void printTestRunFinished(TestRunFinished event) {
poppedNodes(emptyStack).forEach(node -> finishNode(timestamp, node));
currentStack = emptyStack;

printBeforeAfterAllResult(event, timestamp);
print(TEMPLATE_TEST_RUN_FINISHED, timestamp);
}

private void printBeforeAfterAllResult(TestRunFinished event, String timestamp) {
Throwable error = event.getResult().getError();
if (error == null) {
return;
}
// Use dummy test to display before all after all failures
String name = "Before All/After All";
print(TEMPLATE_BEFORE_ALL_AFTER_ALL_STARTED, timestamp, name);
String details = printStackTrace(error);
print(TEMPLATE_BEFORE_ALL_AFTER_ALL_FAILED, timestamp, "Before All/ After All failed", details, name);
print(TEMPLATE_BEFORE_ALL_AFTER_ALL_FINISHED, timestamp, name);
}

private void handleSnippetSuggested(SnippetsSuggestedEvent event) {
suggestions.add(event);
}
Expand Down
39 changes: 34 additions & 5 deletions core/src/main/java/io/cucumber/core/runner/CachingGlue.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.cucumber.core.backend.ParameterTypeDefinition;
import io.cucumber.core.backend.ScenarioScoped;
import io.cucumber.core.backend.StackTraceElementReference;
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 @@ -48,21 +49,27 @@

final class CachingGlue implements Glue {

private static final Comparator<CoreHookDefinition> ASCENDING = Comparator
private static final Comparator<CoreHookDefinition> HOOK_ORDER_ASCENDING = Comparator
.comparingInt(CoreHookDefinition::getOrder)
.thenComparing(ScenarioScoped.class::isInstance);

private static final Comparator<StaticHookDefinition> STATIC_HOOK_ORDER_ASCENDING = Comparator
.comparingInt(StaticHookDefinition::getOrder);

private final List<ParameterTypeDefinition> parameterTypeDefinitions = new ArrayList<>();
private final List<DataTableTypeDefinition> dataTableTypeDefinitions = new ArrayList<>();
private final List<DefaultParameterTransformerDefinition> defaultParameterTransformers = new ArrayList<>();
private final List<CoreDefaultDataTableEntryTransformerDefinition> defaultDataTableEntryTransformers = new ArrayList<>();
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
Expand All @@ -79,6 +86,18 @@ final class CachingGlue implements Glue {
this.bus = bus;
}

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

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

@Override
public void addStepDefinition(StepDefinition stepDefinition) {
stepDefinitions.add(stepDefinition);
Expand All @@ -87,25 +106,25 @@ public void addStepDefinition(StepDefinition stepDefinition) {
@Override
public void addBeforeHook(HookDefinition hookDefinition) {
beforeHooks.add(CoreHookDefinition.create(hookDefinition));
beforeHooks.sort(ASCENDING);
beforeHooks.sort(HOOK_ORDER_ASCENDING);
}

@Override
public void addAfterHook(HookDefinition hookDefinition) {
afterHooks.add(CoreHookDefinition.create(hookDefinition));
afterHooks.sort(ASCENDING);
afterHooks.sort(HOOK_ORDER_ASCENDING);
}

@Override
public void addBeforeStepHook(HookDefinition hookDefinition) {
beforeStepHooks.add(CoreHookDefinition.create(hookDefinition));
beforeStepHooks.sort(ASCENDING);
beforeStepHooks.sort(HOOK_ORDER_ASCENDING);
}

@Override
public void addAfterStepHook(HookDefinition hookDefinition) {
afterStepHooks.add(CoreHookDefinition.create(hookDefinition));
afterStepHooks.sort(ASCENDING);
afterStepHooks.sort(HOOK_ORDER_ASCENDING);
}

@Override
Expand Down Expand Up @@ -143,6 +162,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 @@ -163,6 +186,12 @@ Collection<CoreHookDefinition> getAfterStepHooks() {
return hooks;
}

List<StaticHookDefinition> getAfterAllHooks() {
ArrayList<StaticHookDefinition> hooks = new ArrayList<>(afterAllHooks);
Collections.reverse(hooks);
return hooks;
}

Collection<ParameterTypeDefinition> getParameterTypeDefinitions() {
return parameterTypeDefinitions;
}
Expand Down