io.github.carousell.cucumber2junit.FeatureParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cucumber2junit-maven-plugin Show documentation
Show all versions of cucumber2junit-maven-plugin Show documentation
Generates plain JUnit tests from Gherkin feature files. This is useful when you want to execute Cucumber tests in an environment which does not allow custom JUnit runners, e.g. the AWS Device Farm.
The newest version!
package io.github.carousell.cucumber2junit;
import io.github.carousell.cucumber2junit.model.Feature;
import io.github.carousell.cucumber2junit.model.Scenario;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Parser for feature files. */
public class FeatureParser {
private static final Logger LOG = LoggerFactory.getLogger(FeatureParser.class);
private static HashMap testCasesMap;
private TagsResolver tagsResolver;
public FeatureParser(TagsResolver tagsResolver) {
this.tagsResolver = tagsResolver;
}
// keep track of all scenarios in all processed feature files
private List scenarioIds;
public List getScenarioIds() {
return scenarioIds;
}
/**
* Parse features from a given directory. Only scenarios which have at least one of the specified
* tags will be included. Scenarios "inherit" the tags of the feature they are defined in.
*
* @param path directory containing the feature files
* @param tags {@link List} of tags to include
* @return {@link List} of {@link Feature}s
*/
public List parseFeatures(String path, String... tags) {
// reset scenario id list
scenarioIds = new ArrayList();
List features = new ArrayList();
LOG.info("Recursively parsing feature files from {}", path);
try (Stream paths = Files.walk(Paths.get(path))) {
paths
.filter(Files::isRegularFile)
.forEach(x -> parseFeature(x, Paths.get(path), features, tags));
} catch (IOException e) {
LOG.error("Couldn't load feature files from directory {}", path);
}
return features;
}
private List parseFeature(
Path path, Path rootPath, List features, String... tags) {
LOG.info("Processing feature file {}", path);
try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) {
List scenarios = new ArrayList();
List includedTags = parseRequiredTags(tags).getLeft();
List excludedTags = parseRequiredTags(tags).getRight();
String featureName = "";
String line = "";
String previousLine = "";
int lineNumber = 1;
LOG.info("Required tags: {}, forbidden tags: {}", includedTags, excludedTags);
while ((line = reader.readLine()) != null) {
if (line.trim().startsWith("Feature")) {
featureName = parseFeatureName(line);
}
if (line.trim().startsWith("Scenario")) {
Scenario scenario = parseScenario(line, previousLine, lineNumber);
if (includedTags.stream().allMatch(scenario.getTags()::contains)
&& excludedTags.stream().noneMatch(scenario.getTags()::contains)) {
LOG.info("Adding scenario {} with tags {}", scenario, scenario.getTags());
scenarios.add(scenario);
scenarioIds.add(scenario.getId());
} else {
LOG.debug("Skipping scenario {}", scenario);
}
}
previousLine = line;
lineNumber++;
}
if (!scenarios.isEmpty()) {
features.add(new Feature(featureName, rootPath.relativize(path).toString(), scenarios));
}
if (scenarios.size() == 1) {
LOG.info("1 matching scenario added");
} else {
LOG.info("{} matching scenarios added", scenarios.size());
}
} catch (IOException e) {
LOG.warn("Error reading feature file {}. Skipping!");
}
return features;
}
// the left side of the Pair includes the required tags, the right side the forbidden ones
private Pair, List> parseRequiredTags(String[] input) {
List requiredTags = new ArrayList();
List forbiddenTags = new ArrayList();
Pair, List> tags = Pair.of(requiredTags, forbiddenTags);
for (String tag : input) {
if (tag.startsWith("~")) {
forbiddenTags.add(tag.substring(1));
} else {
requiredTags.add(tag);
}
}
return tags;
}
static String parseFeatureName(String line) {
return line.replaceAll("Feature: ", "").trim();
}
static String parseScenarioName(String line) {
return line.replaceAll("Scenario( Outline)?: ", "").trim();
}
Scenario parseScenario(String line, String previousLine, int lineNumber) {
String scenarioId = parseScenarioId(previousLine);
return new Scenario(scenarioId, lineNumber, parseScenarioName(line), parseTags(scenarioId));
}
String parseScenarioId(String line) {
LOG.debug("Parsing scenario from line {}", line);
String scenarioId = null;
if (line.isEmpty()) {
return null;
} else {
String[] tagsArray = line.trim().replaceAll(" +", " ").split(" ");
for (String tag : tagsArray) {
if (!tag.isEmpty() && tag.trim().startsWith("@case(")) {
scenarioId = parseCaseId(tag);
break;
}
}
return scenarioId;
}
}
List parseTags(String scenarioId) {
if (scenarioId == null || scenarioId.isEmpty() || scenarioId.split(",").length > 1) {
return Collections.emptyList();
}
List tags = tagsResolver.resolveTags(scenarioId);
tags.replaceAll(String::trim);
LOG.info("Using tags {}", tags);
return tags;
}
private String parseCaseId(String tag) {
LOG.debug("Parsing case id from tag {}", tag);
Matcher m = Pattern.compile("\\(([^)]+)\\)").matcher(tag);
if (m.find()) {
return m.group(1).trim();
} else {
return null;
}
}
}