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

cucumber.runtime.StepDefinitionMatch Maven / Gradle / Ivy

There is a newer version: 4.4.0
Show newest version
package cucumber.runtime;

import static cucumber.util.FixJava.map;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import cucumber.api.DataTable;
import cucumber.api.Scenario;
import cucumber.runtime.table.TableConverter;
import cucumber.runtime.xstream.LocalizedXStreams;
import cucumber.util.Mapper;
import gherkin.pickles.PickleCell;
import gherkin.pickles.PickleRow;
import gherkin.pickles.PickleStep;
import gherkin.pickles.PickleString;
import gherkin.pickles.PickleTable;

/**
 * This class wait cucumber PR: https://github.com/cucumber/cucumber-jvm/pull/1056
 *
 * @author sgrillon
 */
public class StepDefinitionMatch extends Match implements DefinitionMatch {
    private final StepDefinition stepDefinition;
    private final transient String featurePath;
    // The official JSON gherkin format doesn't have a step attribute, so we're marking this as transient
    // to prevent it from ending up in the JSON.
    private final transient PickleStep step;
    private final LocalizedXStreams localizedXStreams;

    public StepDefinitionMatch(List arguments, StepDefinition stepDefinition, String featurePath, PickleStep step, LocalizedXStreams localizedXStreams) {
        super(arguments, stepDefinition.getLocation(false));
        this.stepDefinition = stepDefinition;
        this.featurePath = featurePath;
        this.step = step;
        this.localizedXStreams = localizedXStreams;
    }

    @Override
    public void runStep(String language, Scenario scenario) throws Throwable {
        try {
            stepDefinition.execute(language, transformedArgs(step, localizedXStreams.get(localeFor(language))));
        } catch (CucumberException e) {
            throw e;
        } catch (Throwable t) {
            throw removeFrameworkFramesAndAppendStepLocation(t, getStepLocation());
        }
    }

    @Override
    public void dryRunStep(String language, Scenario scenario) throws Throwable {
        // Do nothing
    }

    /**
     * @param step
     *            the step to run
     * @param xStream
     *            used to convert a string to declared stepdef arguments
     * @return an Array matching the types or {@code parameterTypes}, or an array of String if {@code parameterTypes} is null
     */
    private Object[] transformedArgs(PickleStep step, LocalizedXStreams.LocalizedXStream xStream) {
        int argumentCount = getArguments().size();

        if (!step.getArgument().isEmpty()) {
            argumentCount++;
        }
        Integer parameterCount = stepDefinition.getParameterCount();
        if (parameterCount != null && (argumentCount > parameterCount || argumentCount + 1 < parameterCount)) {
            throw arityMismatch(parameterCount);
        }

        List result = new ArrayList<>();

        int n = 0;
        for (Argument a : getArguments()) {
            ParameterInfo parameterInfo = getParameterType(n, String.class);
            Object arg = parameterInfo.convert(a.getVal(), xStream);
            result.add(arg);
            n++;
        }

        if (!step.getArgument().isEmpty()) {
            gherkin.pickles.Argument stepArgument = step.getArgument().get(0);
            if (stepArgument instanceof PickleTable) {
                result.add(tableArgument((PickleTable) stepArgument, n, xStream));
            } else if (stepArgument instanceof PickleString) {
                ParameterInfo parameterInfo = getParameterType(n, String.class);
                Object arg = parameterInfo.convert(((PickleString) stepArgument).getContent(), xStream);
                result.add(arg);
            }
        }

        if (parameterCount != null && argumentCount + 1 == parameterCount) {
            Object obj;
            if (getParameterType(n, DataTable.class).getType().toString().startsWith("java.util.List<")) {
                obj = new ArrayList<>();
            } else if (getParameterType(n, DataTable.class).getType().toString().startsWith("java.util.Map<")) {
                obj = new HashMap<>();
            } else {
                throw arityMismatch(parameterCount);
            }
            result.add(obj);
        }

        return result.toArray(new Object[result.size()]);
    }

    private ParameterInfo getParameterType(int n, Type argumentType) {
        ParameterInfo parameterInfo = stepDefinition.getParameterType(n, argumentType);
        if (parameterInfo == null) {
            // Some backends return null because they don't know
            parameterInfo = new ParameterInfo(argumentType, null, null, false, null);
        }
        return parameterInfo;
    }

    private Object tableArgument(PickleTable stepArgument, int argIndex, LocalizedXStreams.LocalizedXStream xStream) {
        ParameterInfo parameterInfo = getParameterType(argIndex, DataTable.class);
        TableConverter tableConverter = new TableConverter(xStream, parameterInfo);
        DataTable table = new DataTable(stepArgument, tableConverter);
        Type type = parameterInfo.getType();
        return tableConverter.convert(table, type, parameterInfo.isTransposed());
    }

    private CucumberException arityMismatch(int parameterCount) {
        List arguments = createArgumentsForErrorMessage(step);
        return new CucumberException(
                String.format("Arity mismatch: Step Definition '%s' with pattern [%s] is declared with %s parameters. However, the gherkin step has %s arguments %s. \nStep text: %s",
                        stepDefinition.getLocation(true), stepDefinition.getPattern(), parameterCount, arguments.size(), arguments, step.getText()));
    }

    private List createArgumentsForErrorMessage(PickleStep step) {
        List arguments = new ArrayList<>(getArguments());
        if (!step.getArgument().isEmpty()) {
            gherkin.pickles.Argument stepArgument = step.getArgument().get(0);
            if (stepArgument instanceof PickleString) {
                arguments.add(new Argument(-1, "DocString:" + ((PickleString) stepArgument).getContent()));
            } else if (stepArgument instanceof PickleTable) {
                List> rows = map(((PickleTable) stepArgument).getRows(), new Mapper>() {
                    @Override
                    public List map(PickleRow row) {
                        List raw = new ArrayList<>(row.getCells().size());
                        for (PickleCell pickleCell : row.getCells()) {
                            raw.add(pickleCell.getValue());
                        }
                        return raw;
                    }
                });
                arguments.add(new Argument(-1, "Table:" + rows.toString()));
            }
        }
        return arguments;
    }

    protected Throwable removeFrameworkFramesAndAppendStepLocation(Throwable error, StackTraceElement stepLocation) {
        StackTraceElement[] stackTraceElements = error.getStackTrace();
        if (stackTraceElements.length == 0 || stepLocation == null) {
            return error;
        }

        int newStackTraceLength;
        for (newStackTraceLength = 1; newStackTraceLength < stackTraceElements.length; ++newStackTraceLength) {
            if (stepDefinition.isDefinedAt(stackTraceElements[newStackTraceLength - 1])) {
                break;
            }
        }
        StackTraceElement[] newStackTrace = new StackTraceElement[newStackTraceLength + 1];
        System.arraycopy(stackTraceElements, 0, newStackTrace, 0, newStackTraceLength);
        newStackTrace[newStackTraceLength] = stepLocation;
        error.setStackTrace(newStackTrace);
        return error;
    }

    private Locale localeFor(String language) {
        String[] languageAndCountry = language.split("-");
        if (languageAndCountry.length == 1) {
            return new Locale(language);
        } else {
            return new Locale(languageAndCountry[0], languageAndCountry[1]);
        }
    }

    @Override
    public String getPattern() {
        return stepDefinition.getPattern();
    }

    public StackTraceElement getStepLocation() {
        return new StackTraceElement("✽", step.getText(), featurePath, getStepLine(step));
    }

    @Override
    public Match getMatch() {
        return this;
    }

    @Override
    public String getCodeLocation() {
        return stepDefinition.getLocation(false);
    }

    public static int getStepLine(PickleStep step) {
        return step.getLocations().get(step.getLocations().size() - 1).getLine();
    }
}