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

com.technophobia.substeps.runner.ExecutionNodeRunner Maven / Gradle / Ivy

There is a newer version: 1.1.8
Show newest version
/*
 *  Copyright Technophobia Ltd 2012
 *
 *   This file is part of Substeps.
 *
 *    Substeps is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Substeps is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with Substeps.  If not, see .
 */
package com.technophobia.substeps.runner;

import com.google.common.collect.Lists;
import com.technophobia.substeps.execution.DryRunImplementationCache;
import com.technophobia.substeps.execution.ExecutionResult;
import com.technophobia.substeps.execution.ImplementationCache;
import com.technophobia.substeps.execution.MethodExecutor;
import com.technophobia.substeps.execution.node.*;
import com.technophobia.substeps.model.*;
import com.technophobia.substeps.model.exception.NoTestsRunException;
import com.technophobia.substeps.runner.builder.ExecutionNodeTreeBuilder;
import com.technophobia.substeps.runner.node.RootNodeRunner;
import com.technophobia.substeps.runner.setupteardown.SetupAndTearDown;
import com.technophobia.substeps.runner.syntax.SyntaxBuilder;
import com.typesafe.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.substeps.report.ReportingUtil;
import org.substeps.runner.NewSubstepsExecutionConfig;

import java.io.File;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Takes a tree of execution nodes and executes them, all variables, args,
 * backgrounds already pre-determined
 *
 * @author ian
 */
public class ExecutionNodeRunner implements SubstepsRunner {

    private static final String DRY_RUN_KEY = "dryRun";

    private static final Logger log = LoggerFactory.getLogger(ExecutionNodeRunner.class);

    private RootNode rootNode;

    private final INotificationDistributor notificationDistributor = new NotificationDistributor();

    private RootNodeExecutionContext nodeExecutionContext;

    private final MethodExecutor methodExecutor = new ImplementationCache();

    private final RootNodeRunner rootNodeRunner = new RootNodeRunner();

    private List failures;

    private final ReportingUtil reportingUtil = new ReportingUtil();

    // map of nodes to each of the parents, where this node is used
    private final Map> callerHierarchy = new HashMap>();

    @Override
    public void addNotifier(final IExecutionListener notifier) {

        this.notificationDistributor.addListener(notifier);
    }

    public RootNode prepareExecutionConfig(final Config config , final Syntax syntax, final TestParameters parameters,
                                           final SetupAndTearDown setupAndTearDown ,
                                           final MethodExecutor methodExecutorToUse,
                                           TagManager nonFatalTagmanager ) {


        final ExecutionNodeTreeBuilder nodeTreeBuilder = new ExecutionNodeTreeBuilder(parameters, config);

        // building the tree can throw critical failures if exceptions are found
        this.rootNode = nodeTreeBuilder.buildExecutionNodeTree(NewSubstepsExecutionConfig.getDescription(config));

        setupExecutionListeners(NewSubstepsExecutionConfig.getExecutionListenerClasses(config));



        if (NewSubstepsExecutionConfig.isCheckForUncalledAndUnused(config)) {
            processUncalledAndUnused(syntax, NewSubstepsExecutionConfig.getDataOutputDirectory(config));
        }

        ExecutionContext.put(Scope.SUITE, INotificationDistributor.NOTIFIER_DISTRIBUTOR_KEY,
                this.notificationDistributor);


        this.nodeExecutionContext = new RootNodeExecutionContext(this.notificationDistributor,
                Lists.newArrayList(), setupAndTearDown, nonFatalTagmanager,
                methodExecutorToUse);

        return this.rootNode;

    }

    public static Class[] buildInitialisationClassList(List> stepImplClassList, List> initialisationClassList){

        List> finalInitialisationClassList = new ArrayList<>();

        // add the explicitly set init class list at the front
        if (initialisationClassList != null){
            finalInitialisationClassList.addAll(initialisationClassList);
        }

        if (stepImplClassList != null) {

            final InitialisationClassSorter orderer = new InitialisationClassSorter();

            for (final Class c : stepImplClassList) {

                final SubSteps.StepImplementations annotation = c.getAnnotation(SubSteps.StepImplementations.class);

                if (annotation != null) {
                    final Class[] initClasses = annotation.requiredInitialisationClasses();

                    if (initClasses != null) {

                        orderer.addOrderedInitialisationClasses(initClasses);
                    }
                }
            }
            finalInitialisationClassList.addAll(orderer.getOrderedList());
        }

        if (!finalInitialisationClassList.isEmpty()) {
            return finalInitialisationClassList.toArray(new Class[]{});
        }
        else {
            return null;
        }
    }


    @Override
    public RootNode prepareExecutionConfig(Config cfg) {

        NewSubstepsExecutionConfig.setThreadLocalConfig(cfg);


        final String dryRunProperty = System.getProperty(DRY_RUN_KEY);
        final boolean dryRun = dryRunProperty != null && Boolean.parseBoolean(dryRunProperty);

        final MethodExecutor methodExecutorToUse = dryRun ? new DryRunImplementationCache() : this.methodExecutor;

        if (dryRun) {
            log.info("**** DRY RUN ONLY **");
        }

        List> stepImplementationClasses = NewSubstepsExecutionConfig.getStepImplementationClasses(cfg);
        Class[] initialisationClasses = NewSubstepsExecutionConfig.getInitialisationClasses(cfg);

        ArrayList> initClassList = null;
        if (initialisationClasses != null) {
            initClassList = Lists.newArrayList(initialisationClasses);
        }

        Class[] finalInitClasses = buildInitialisationClassList(stepImplementationClasses, initClassList);

        final SetupAndTearDown setupAndTearDown = new SetupAndTearDown(finalInitClasses,
                methodExecutorToUse);


        final String loggingConfigName = NewSubstepsExecutionConfig.getDescription(cfg);

        setupAndTearDown.setLoggingConfigName(loggingConfigName);

        final TagManager tagmanager = new TagManager(NewSubstepsExecutionConfig.getTags(cfg));

        final TagManager nonFatalTagmanager = TagManager.fromTags(NewSubstepsExecutionConfig.getNonFatalTags(cfg));

        File subStepsFile = null;

        if (NewSubstepsExecutionConfig.getSubStepsFileName(cfg) != null) {
            subStepsFile = new File(NewSubstepsExecutionConfig.getSubStepsFileName(cfg));
        }

        final Syntax syntax = SyntaxBuilder.buildSyntax(stepImplementationClasses, subStepsFile,
                NewSubstepsExecutionConfig.isStrict(cfg), NewSubstepsExecutionConfig.getNonStrictKeywordPrecedence(cfg));

        final TestParameters parameters = new TestParameters(tagmanager, syntax, NewSubstepsExecutionConfig.getFeatureFile(cfg),
                NewSubstepsExecutionConfig.getScenarioName(cfg));

        parameters.setFailParseErrorsImmediately(NewSubstepsExecutionConfig.isFastFailParseErrors(cfg));
        parameters.init();

        return prepareExecutionConfig(cfg, syntax, parameters, setupAndTearDown, methodExecutorToUse, nonFatalTagmanager);
    }


    private void setupExecutionListeners( final List> executionListenerClasses) {
        // add any listeners (including the step execution logger)

        // TODO - pass the base dir in or get from config

        for (final Class listener : executionListenerClasses) {

            log.info("adding executionListener: " + listener.getClass());

            try {
                this.notificationDistributor.addListener(listener.newInstance());
            } catch (final Exception e) {
                // not the end of the world...
                log.warn("failed to instantiate ExecutionListener: " + listener.getClass(), e);
            }
        }
    }

    /**
     * @param syntax
     */
    private void processUncalledAndUnused(final Syntax syntax, final File dataOutputDir) {
        final List uncalledStepImplementations = syntax.getUncalledStepImplementations();

        if (!dataOutputDir.exists() && !dataOutputDir.mkdir()){
            log.error("failed to create directory: " + dataOutputDir.getAbsolutePath());
        }


        reportingUtil.writeUncalledStepImpls(uncalledStepImplementations, dataOutputDir);

        buildCallHierarchy();

        checkForUncalledParentSteps(syntax, dataOutputDir);

    }

    /**
     * @param syntax
     */
    private void checkForUncalledParentSteps(final Syntax syntax, File outputDir) {

        final Set calledExecutionNodes = callerHierarchy.keySet();

        List uncalledSubstepDefs = new ArrayList<>();

        for (final ParentStep p : syntax.getSortedRootSubSteps()) {

            // is there an executionnodeusage that is going to match ?

            final Step parent = p.getParent();

            if (thereIsNotAStepThatMatchesThisPattern(parent.getPattern(), calledExecutionNodes)) {
                uncalledSubstepDefs.add(parent);
            }
        }
        reportingUtil.writeUncalledStepDefs(uncalledSubstepDefs, outputDir);
    }

    private boolean thereIsNotAStepThatMatchesThisPattern(final String stepPattern, final Set calledExecutionNodes) {
        boolean found = false;

        final Iterator it = calledExecutionNodes.iterator();

        while (it.hasNext() && !found) {
            final ExecutionNodeUsage u = it.next();

            if (stepPattern != null && u.getDescription() != null) {

                found = Pattern.matches(stepPattern, u.getDescription());
            }

        }
        // NB. return true if no match found!
        return !found;
    }


    private void buildCallHierarchy() {

        final ExecutionNodeUsage rootUsage = new ExecutionNodeUsage(this.rootNode);

        callerHierarchy.put(rootUsage, null); // nothing calls this

        for (final FeatureNode feature : this.rootNode.getChildren()) {

            addToCallHierarchy(feature);

            for (final ScenarioNode scenario : feature.getChildren()) {

                addToCallHierarchy(scenario);

                processChildrenForCallHierarchy(scenario.getChildren());
            }
        }
    }

    private void processChildrenForCallHierarchy(final List children) {
        for (final Object obj : children) {

            final IExecutionNode node = (IExecutionNode) obj;

            addToCallHierarchy(node);

            log.trace("looking at node description: " + node.getDescription() + " line: " + node.getLine());

            if (NodeWithChildren.class.isAssignableFrom(node.getClass())) {
                final NodeWithChildren nodeWithChildren = (NodeWithChildren) node;
                log.trace("proccessing children...");
                processChildrenForCallHierarchy(nodeWithChildren.getChildren());
            }
        }
    }

    /**
     * @param node
     */
    private void addToCallHierarchy(final IExecutionNode node) {

        final ExecutionNodeUsage usage = new ExecutionNodeUsage(node);

        log.trace("building usage for desc: " + node.getDescription() + " line: " + node.getLine());

        List immediateParents = callerHierarchy.get(usage);
        if (immediateParents == null) {
            log.trace("no uses already for node...");
            immediateParents = new ArrayList();
            callerHierarchy.put(usage, immediateParents);
        }
        log.trace("adding used by descr: " + node.getParent().getDescription() + " line: " + node.getParent().getLine());

        immediateParents.add(new ExecutionNodeUsage(node.getParent()));

    }

    @Override
    public RootNode run() {

        // TODO - why is this here twice?
        ExecutionContext.put(Scope.SUITE, INotificationDistributor.NOTIFIER_DISTRIBUTOR_KEY,
                this.notificationDistributor);

        this.rootNodeRunner.run(this.rootNode, this.nodeExecutionContext);

        if (!this.nodeExecutionContext.haveTestsBeenRun()) {

            final Throwable t = new NoTestsRunException();

            SubstepExecutionFailure sef = new SubstepExecutionFailure(t, this.rootNode, ExecutionResult.FAILED);

            this.notificationDistributor.onNodeFailed(this.rootNode, t);

            this.nodeExecutionContext.addFailure(sef);
        }

        this.failures = this.nodeExecutionContext.getFailures();

        return this.rootNode;
    }

    @Override
    public List getFailures() {

        return this.failures;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy