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

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

/*
 *	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 java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.technophobia.substeps.execution.ExecutionNode;
import com.technophobia.substeps.execution.ImplementationCache;
import com.technophobia.substeps.execution.MethodExecutor;
import com.technophobia.substeps.model.Scope;
import com.technophobia.substeps.model.Syntax;
import com.technophobia.substeps.runner.setupteardown.SetupAndTearDown;
import com.technophobia.substeps.runner.syntax.SyntaxBuilder;

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

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

    private boolean noTestsRun = true;

    private boolean dryRun;

    private ExecutionNode rootNode;
    private final INotificationDistributor notificationDistributor = new NotificationDistributor();
    private SetupAndTearDown setupAndTearDown;
    private ExecutionConfig config;
    private TagManager nonFatalTagmanager = null;

    private final MethodExecutor methodExecutor = new ImplementationCache();

    public void addNotifier(final INotifier notifier) {

        notificationDistributor.addListener(notifier);
    }

    public ExecutionNode prepareExecutionConfig(final ExecutionConfig theConfig) {

        config = theConfig;
        config.initProperties();

        setupAndTearDown = new SetupAndTearDown(config.getInitialisationClasses(), methodExecutor);

        final String loggingConfigName = config.getDescription() != null ? config.getDescription() : "SubStepsMojo";

        setupAndTearDown.setLoggingConfigName(loggingConfigName);

        final TagManager tagmanager = new TagManager(config.getTags());

        if (config.getNonFatalTags() != null) {
            nonFatalTagmanager = new TagManager(config.getNonFatalTags());
        }

        File subStepsFile = null;

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

        final Syntax syntax = SyntaxBuilder.buildSyntax(config.getStepImplementationClasses(), subStepsFile,
                config.isStrict(), config.getNonStrictKeywordPrecedence());

        final TestParameters parameters = new TestParameters(tagmanager, syntax, config.getFeatureFile());

        parameters.setFailParseErrorsImmediately(config.isFastFailParseErrors());
        parameters.init();

        final ExecutionNodeTreeBuilder nodeTreeBuilder = new ExecutionNodeTreeBuilder(parameters);

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

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

        final String dryRunProperty = System.getProperty("dryRun");
        if (dryRunProperty != null && Boolean.parseBoolean(dryRunProperty)) {

            log.info("**** DRY RUN ONLY **");

            setupAndTearDown.setDryRun(true);
            setDryRun(true);
        }

        return rootNode;
    }

    public List run() {
        log.debug("run root node");
        noTestsRun = true;

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

        final List failures = runExecutionNodeHierarchy(Scope.SUITE, rootNode);

        if (noTestsRun) {

            final Throwable t = new IllegalStateException("No tests executed");
            rootNode.getResult().setFailed(t);
            notificationDistributor.notifyNodeFailed(rootNode, t);

            addFailure(failures, new SubstepExecutionFailure(t, rootNode));

        }

        return failures;
    }

    private void addFailure(final List failures, final SubstepExecutionFailure failure) {
        failures.add(failure);
        logFailure(failure);

        // set the criticality of this failure

        if (!failure.isSetupOrTearDown() && this.nonFatalTagmanager != null
                && nonFatalTagmanager.acceptTaggedScenario(failure.getExeccutionNode().getTagsFromHierarchy())) {

            failure.setNonCritical(true);
        }

    }

    private List runExecutionNodeHierarchy(final Scope scope, final ExecutionNode node) {

        log.info("run Node Hierarchy @ " + scope.name() + ":" + node.getDebugStringForThisNode());

        final List failures = Lists.newArrayList();

        notificationDistributor.notifyNodeStarted(node);

        // node may have parsing error, in which case, bail immediately
        if (node.hasError()) {

            notificationDistributor.notifyNodeFailed(node, node.getResult().getThrown());
            addFailure(failures, new SubstepExecutionFailure(node.getResult().getThrown(), node));

        } else {

            node.getResult().setStarted();

            runSetupIfNecessary(scope, node, failures);

            runBackgroundNodes(node, failures);

            if (failures.isEmpty()) {
                executeNodeOrRunChildren(scope, node, failures);
            }

            runTearDown(scope, node, failures);

            recordResult(node, failures);
        }
        return failures;
    }

    private void runSetupIfNecessary(final Scope scope, final ExecutionNode node,
            final List failures) {

        try {
            if (!node.isOutlineScenario()) {
                setupAndTearDown.runSetup(scope);
            }
        } catch (final Throwable t) {
            log.debug("setup failed", t);

            addFailure(failures, new SubstepExecutionFailure(t, node, true));
        }
    }

    private void runBackgroundNodes(final ExecutionNode node, final List failures) {
        if (failures.isEmpty() && node.hasBackground() && !node.isOutlineScenario()) {
            // any of these fail then bail & mark this node as failed

            Iterator backgroundNodesIt = node.getBackgrounds().iterator();

            while (failures.isEmpty() && backgroundNodesIt.hasNext()) {

                final List backgroundScenarioFailures = runExecutionNodeHierarchy(
                        Scope.SCENARIO_BACKGROUND, backgroundNodesIt.next());

                if (!backgroundScenarioFailures.isEmpty()) {

                    log.debug("running background scenarios failed");
                    failures.addAll(backgroundScenarioFailures);
                }
            }
        }
    }

    private void executeNodeOrRunChildren(final Scope scope, final ExecutionNode node,
            final List failures) {

        if (node.isExecutable()) {

            final SubstepExecutionFailure methodInvocationFailure = executeNodeMethod(node);
            if (methodInvocationFailure != null) {

                addFailure(failures, methodInvocationFailure);
            }

        } else {
            runChildren(scope, node, failures);
        }
    }

    private void runChildren(final Scope scope, final ExecutionNode node, final List failures) {

        if (node.shouldHaveChildren() && !node.hasChildren()) {
            // TODO - better error message required
            addFailure(failures, new SubstepExecutionFailure(new IllegalStateException(
                    "node should have children but doesn't"), node));
        } else if (node.hasChildren()) {
            log.debug("node has children");
            // if any fail, mark this as failed. if current scope is
            // suite, feature then continue even if failure
            // if scenario or outline or step, bail
            for (final ExecutionNode child : node.getChildren()) {

                final Scope childScope = getChildScope(node, scope);

                final List childFailures = runExecutionNodeHierarchy(childScope, child);

                if (!childFailures.isEmpty()) {
                    log.debug("running children failed");
                    failures.addAll(childFailures);
                }

                // bail out if current scope is Step or SCENARIO_OUTLINE_ROW

                // bail if current scope is scenario and childscope is step
                if (!failures.isEmpty()
                        && (scope == Scope.STEP || scope == Scope.SCENARIO_OUTLINE_ROW || (scope == Scope.SCENARIO && childScope == Scope.STEP))) {
                    log.debug("bailing out of execution");
                    break;
                }
            }
        }
    }

    private void runTearDown(final Scope scope, final ExecutionNode node, final List failures) {
        try {
            // run tear down if necessary for this depth and step
            if (!node.isOutlineScenario()) {
                setupAndTearDown.runTearDown(scope);
                ExecutionContext.clear(scope);

            }
        } catch (final Throwable t) {
            log.debug("tear down failed", t);

            failures.add(new SubstepExecutionFailure(t, node, true));
        }
    }

    private void recordResult(final ExecutionNode node, final List failures) {
        if (failures.isEmpty()) {
            log.debug("node success");
            notificationDistributor.notifyNodeFinished(node);

            node.getResult().setFinished();

        } else {

            log.debug("node failures");
            // just notify on the last one in..?
            final Throwable lastException = failures.get(failures.size() - 1).getCause();
            notificationDistributor.notifyNodeFailed(node, lastException);

            // TODO should this have been set earlier...?
            node.getResult().setFailed(lastException);
        }
    }

    /**
     * @param failure
     */
    private void logFailure(final SubstepExecutionFailure failure) {

        final Throwable failureCause = failure.getCause();
        final Throwable here = new Throwable();

        final StackTraceElement[] failureTrace = failureCause.getStackTrace();
        final StackTraceElement[] hereTrace = here.getStackTrace();

        final int requiredTraceSize = failureTrace.length - hereTrace.length;

        if (requiredTraceSize > 0 && requiredTraceSize < failureTrace.length) {

            final StringBuilder stackTraceBuilder = new StringBuilder();

            stackTraceBuilder.append(failureCause.toString()).append("\n");

            for (int i = 0; i < requiredTraceSize; i++) {
                stackTraceBuilder.append("\tat ").append(failureTrace[i]).append("\n");
            }

            log.info("SubstepExecutionFailure @ " + failure.getExeccutionNode().getDebugStringForThisNode() + "\n"
                    + stackTraceBuilder.toString());

        } else {
            // fallback position - just normal logging
            log.info("SubstepExecutionFailure @ " + failure.getExeccutionNode().getDebugStringForThisNode(),
                    failureCause);
        }

    }

    /**
     * @param node
     * @param theException
     * @return
     */
    private SubstepExecutionFailure executeNodeMethod(final ExecutionNode node) {

        SubstepExecutionFailure theFailure = null;
        log.debug("executing node method");

        // if executable invoke
        try {

            if (!dryRun) {
                methodExecutor.executeMethod(node.getTargetClass(), node.getTargetMethod(), node.getMethodArgs());
            }
            noTestsRun = false;

        } catch (final InvocationTargetException e) {

            theFailure = new SubstepExecutionFailure(e.getTargetException(), node);

        } catch (final Throwable e) {

            theFailure = new SubstepExecutionFailure(e, node);
        }
        return theFailure;
    }

    private Scope getChildScope(final ExecutionNode node, final Scope currentScope) {
        Scope rtn = null;

        // maybe a neater way of doing this rather than a case statement...
        switch (currentScope) {
            case SUITE: {
                rtn = Scope.FEATURE;
                break;
            }
            case FEATURE: {
                rtn = Scope.SCENARIO;

                break;
            }
            case SCENARIO: {
                // a scenario can go into outline row or steps
                if (node.isOutlineScenario()) {
                    rtn = Scope.SCENARIO_OUTLINE_ROW;
                } else {
                    rtn = Scope.STEP;
                }
                break;
            }
            case SCENARIO_BACKGROUND:
            case SCENARIO_OUTLINE_ROW:
            case STEP: {
                rtn = Scope.STEP;
                break;
            }
            default: {
                throw new IllegalStateException("impossible state");
            }

        }

        log.debug("child scope: " + rtn.name() + " for: " + currentScope.name());

        return rtn;
    }

    public void setDryRun(final boolean dryRun) {
        this.dryRun = dryRun;
    }

    // public void setNotifier(final INotifier notifier) {
    // notificationDistributor = notifier;
    // }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy