Skip to content

Support attachments and hooks #43

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

Draft
wants to merge 2 commits 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
98 changes: 70 additions & 28 deletions java/src/main/java/io/cucumber/query/NamingStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,68 +109,99 @@ private NamingStrategy() {

abstract String name(GherkinDocumentElements elements, Pickle pickle);


private static String exampleNumber(GherkinDocumentElements elements, Integer index) {
String examplesPrefix = elements.examplesIndex()
.map(examplesIndex -> examplesIndex + 1)
.map(examplesIndex -> examplesIndex + ".")
.orElse("");
return "Example #" + examplesPrefix + (index + 1);
}

private static String join(List<String> pieces) {
return pieces.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(" - "));
}

private static class ShortNamingStrategy extends NamingStrategy {
private final NamingVisitor visitor;
private final ExampleName exampleName;

private ShortNamingStrategy(ExampleName exampleName) {
private ShortNamingStrategy(NamingVisitor visitor, ExampleName exampleName) {
this.visitor = requireNonNull(visitor);
this.exampleName = requireNonNull(exampleName);
}

String name(GherkinDocumentElements elements, Pickle pickle) {
return elements.exampleIndex()
.filter(index -> exampleName == NUMBER)
.map(index -> exampleNumber(elements, index))
.orElseGet(pickle::getName);
.map(index -> {
Integer examplesIndex = elements.examplesIndex().orElse(0);
return visitor.accept(examplesIndex, index);
})
.orElseGet(() -> visitor.accept(pickle));
}

}

private static class LongNamingStrategy extends NamingStrategy {
private final NamingVisitor visitor;
private final CharSequence delimiter;
private final FeatureName featureName;
private final ExampleName exampleName;

private LongNamingStrategy(FeatureName featureName, ExampleName exampleName) {
private LongNamingStrategy(NamingVisitor visitor, CharSequence delimiter, FeatureName featureName, ExampleName exampleName) {
this.visitor = requireNonNull(visitor);
this.delimiter = requireNonNull(delimiter);
this.featureName = requireNonNull(featureName);
this.exampleName = requireNonNull(exampleName);
}

String name(GherkinDocumentElements elements, Pickle pickle) {
List<String> pieces = new ArrayList<>();
elements.feature().map(Feature::getName)
elements.feature().map(visitor::accept)
.filter(feature -> featureName == INCLUDE)
.ifPresent(pieces::add);
elements.rule().map(Rule::getName)
elements.rule().map(visitor::accept)
.ifPresent(pieces::add);
elements.scenario().map(Scenario::getName)
elements.scenario().map(visitor::accept)
.ifPresent(pieces::add);
elements.examples().map(Examples::getName)
elements.examples().map(visitor::accept)
.ifPresent(pieces::add);
elements.exampleIndex()
.map(index -> exampleName == NUMBER ? exampleNumber(elements, index) : pickle.getName())
.map(index -> {
Integer examplesIndex = elements.examplesIndex().orElse(0);
return exampleName == NUMBER ? visitor.accept(examplesIndex, index) : visitor.accept(pickle);
})
.ifPresent(pieces::add);
return join(pieces);

return pieces.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(delimiter));
}
}

public interface NamingVisitor {
default String accept(Feature feature) {
return feature.getName();
}

default String accept(Rule rule) {
return rule.getName();
}

default String accept(Scenario scenario) {
return scenario.getName();
}

default String accept(Examples examples) {
return examples.getName();
}

default String accept(int examplesIndex, int exampleIndex) {
return "Example #" + (examplesIndex + 1) + "." + (exampleIndex + 1);
}

default String accept(Pickle pickle) {
return pickle.getName();
}
}

public static class Builder {
private static class DefaultNamingVisitor implements NamingVisitor {

}

private final Strategy strategy;
private FeatureName featureName = INCLUDE;
private ExampleName exampleName = NUMBER;
private NamingVisitor visitor = new DefaultNamingVisitor();
private CharSequence delimiter = " - ";

public Builder(Strategy strategy) {
this.strategy = requireNonNull(strategy);
Expand All @@ -186,12 +217,23 @@ public Builder featureName(FeatureName featureName) {
return this;
}

public Builder namingVisitor(NamingVisitor visitor) {
this.visitor = requireNonNull(visitor);
return this;
}
public Builder delimiter(CharSequence delimiter) {
this.delimiter = requireNonNull(delimiter);
return this;
}

public NamingStrategy build() {
if (strategy == Strategy.SHORT) {
return new ShortNamingStrategy(exampleName);
return new ShortNamingStrategy(visitor, exampleName);
}
return new LongNamingStrategy(featureName, exampleName);
return new LongNamingStrategy(visitor, delimiter, featureName, exampleName);

}
}


}
71 changes: 69 additions & 2 deletions java/src/main/java/io/cucumber/query/Query.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package io.cucumber.query;

import io.cucumber.messages.Convertor;
import io.cucumber.messages.types.Attachment;
import io.cucumber.messages.types.Envelope;
import io.cucumber.messages.types.Examples;
import io.cucumber.messages.types.Feature;
import io.cucumber.messages.types.GherkinDocument;
import io.cucumber.messages.types.Hook;
import io.cucumber.messages.types.Location;
import io.cucumber.messages.types.Pickle;
import io.cucumber.messages.types.PickleStep;
import io.cucumber.messages.types.Rule;
import io.cucumber.messages.types.Scenario;
import io.cucumber.messages.types.Step;
import io.cucumber.messages.types.StepDefinition;
import io.cucumber.messages.types.TableRow;
import io.cucumber.messages.types.TestCase;
import io.cucumber.messages.types.TestCaseFinished;
Expand All @@ -25,12 +29,14 @@
import java.time.Duration;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
Expand Down Expand Up @@ -58,19 +64,21 @@
* It is safe to query and update this class concurrently.
*
* @see <a href="https://github.com/cucumber/messages?tab=readme-ov-file#message-overview">Cucumber Messages - Message Overview</a>
*
*/
public final class Query {
private final Comparator<TestStepResult> testStepResultComparator = nullsFirst(comparing(o -> o.getStatus().ordinal()));
private final Deque<TestCaseStarted> testCaseStarted = new ConcurrentLinkedDeque<>();
private final Map<String, List<Attachment>> attachmentsByTestStepId = new ConcurrentHashMap<>();
private final Map<String, TestCaseFinished> testCaseFinishedByTestCaseStartedId = new ConcurrentHashMap<>();
private final Map<String, List<TestStepFinished>> testStepsFinishedByTestCaseStartedId = new ConcurrentHashMap<>();
private final Map<String, Pickle> pickleById = new ConcurrentHashMap<>();
private final Map<String, TestCase> testCaseById = new ConcurrentHashMap<>();
private final Map<String, Step> stepById = new ConcurrentHashMap<>();
private final Map<String, TestStep> testStepById = new ConcurrentHashMap<>();
private final Map<String, PickleStep> pickleStepById = new ConcurrentHashMap<>();
private final Map<String, Hook> hookById = new ConcurrentHashMap<>();
private final Map<String, GherkinDocumentElements> gherkinAstNodesById = new ConcurrentHashMap<>();
private final Map<String, StepDefinition> stepDefinitionById = new ConcurrentHashMap<>();
private TestRunStarted testRunStarted;
private TestRunFinished testRunFinished;

Expand Down Expand Up @@ -132,10 +140,44 @@ public List<TestStep> findAllTestSteps() {
.collect(toList());
}

public List<Attachment> findAllAttachmentsBy(TestStep testStep) {
requireNonNull(testStep);
List<Attachment> attachments = attachmentsByTestStepId.getOrDefault(testStep.getId(), emptyList());
// Concurrency
return new ArrayList<>(attachments);
}

public Optional<Feature> findFeatureBy(TestCaseStarted testCaseStarted) {
requireNonNull(testCaseStarted);
return findGherkinAstNodesBy(testCaseStarted).flatMap(GherkinDocumentElements::feature);
}

public Optional<Hook> findHookBy(TestStep testStep) {
requireNonNull(testStep);
return testStep.getHookId()
.map(hookById::get);
}

public Optional<GherkinDocument> findGherkinDocumentBy(TestCaseStarted testCaseStarted) {
requireNonNull(testCaseStarted);
return findGherkinAstNodesBy(testCaseStarted).map(GherkinDocumentElements::document);
}


public Optional<Scenario> findScenarioBy(TestCaseStarted testCaseStarted) {
requireNonNull(testCaseStarted);
return findGherkinAstNodesBy(testCaseStarted).flatMap(GherkinDocumentElements::scenario);
}

public Optional<Location> findLocationOf(Pickle pickle) {
requireNonNull(pickle);
return findGherkinAstNodesBy(pickle)
.flatMap(gherkinDocumentElements -> {
Optional<Location> exampleLocation = gherkinDocumentElements.example().map(TableRow::getLocation);
Optional<Location> scenarioLocation = gherkinDocumentElements.scenario().map(Scenario::getLocation);
return exampleLocation.map(Optional::of).orElse(scenarioLocation);
});
}
public Optional<TestStepResult> findMostSevereTestStepResulBy(TestCaseStarted testCaseStarted) {
requireNonNull(testCaseStarted);
return findTestStepsFinishedBy(testCaseStarted)
Expand All @@ -161,7 +203,7 @@ public Optional<Pickle> findPickleBy(TestCaseStarted testCaseStarted) {
}

public Optional<PickleStep> findPickleStepBy(TestStep testStep) {
requireNonNull(testCaseStarted);
requireNonNull(testStep);
return testStep.getPickleStepId()
.map(pickleStepById::get);
}
Expand All @@ -172,6 +214,15 @@ public Optional<Step> findStepBy(PickleStep pickleStep) {
return ofNullable(stepById.get(stepId));
}

public List<StepDefinition> findStepDefinitionBy(TestStep testStep) {
requireNonNull(testStep);
return testStep.getStepDefinitionIds().map(ids -> ids.stream()
.map(stepDefinitionById::get)
.filter(Objects::nonNull)
.collect(toList()))
.orElseGet(Collections::emptyList);
}

public Optional<TestCase> findTestCaseBy(TestCaseStarted testCaseStarted) {
requireNonNull(testCaseStarted);
return ofNullable(testCaseById.get(testCaseStarted.getTestCaseId()));
Expand Down Expand Up @@ -240,8 +291,16 @@ public void update(Envelope envelope) {
envelope.getTestCaseFinished().ifPresent(this::updateTestCaseFinished);
envelope.getTestStepFinished().ifPresent(this::updateTestStepFinished);
envelope.getGherkinDocument().ifPresent(this::updateGherkinDocument);
envelope.getHook().ifPresent(this::updateHook);
envelope.getPickle().ifPresent(this::updatePickle);
envelope.getStepDefinition().ifPresent(this::updateStepDefinition);
envelope.getTestCase().ifPresent(this::updateTestCase);
envelope.getAttachment().ifPresent(this::updateAttachment);
}

private void updateAttachment(Attachment event) {
event.getTestStepId()
.ifPresent(testStepId -> attachmentsByTestStepId.compute(testStepId, updateList(event)));
}

private Optional<GherkinDocumentElements> findGherkinAstNodesBy(Pickle pickle) {
Expand All @@ -265,11 +324,19 @@ private void updateTestCase(TestCase event) {
event.getTestSteps().forEach(testStep -> testStepById.put(testStep.getId(), testStep));
}

private void updateHook(Hook event) {
this.hookById.put(event.getId(), event);
}

private void updatePickle(Pickle event) {
this.pickleById.put(event.getId(), event);
event.getSteps().forEach(pickleStep -> pickleStepById.put(pickleStep.getId(), pickleStep));
}

private void updateStepDefinition(StepDefinition event) {
this.stepDefinitionById.put(event.getId(), event);
}

private void updateGherkinDocument(GherkinDocument document) {
document.getFeature().ifPresent(feature -> updateFeature(document, feature));
}
Expand Down