All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.carousell.cucumber2junit.FeatureParser Maven / Gradle / Ivy

Go to download

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;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy