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

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

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.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 TagsResolver tagsResolver;

  public FeatureParser(TagsResolver tagsResolver) {
    this.tagsResolver = tagsResolver;
  }

  /**
   * 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) {
    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);
          } 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) {
    return new Scenario(lineNumber, parseScenarioName(line), parseTags(previousLine));
  }

  List parseTags(String line) {
    LOG.debug("Parsing tags from line {}", line);
    String scenarioId = null;
    if (line.isEmpty()) {
      return Collections.emptyList();
    } else {
      String[] tagsArray = line.trim().replaceAll(" +", " ").split(" ");
      for (String tag : tagsArray) {
        if (!tag.isEmpty() && tag.trim().startsWith("@case(")) {
          scenarioId = parseCaseId(tag);
          break;
        }
      }
      if (scenarioId == null || scenarioId.isEmpty()) {
        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);
    } else {
      return null;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy