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

cucumber.runtime.arquillian.backend.ArquillianBackend Maven / Gradle / Ivy

package cucumber.runtime.arquillian.backend;

import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.ObjectFactory;
import cucumber.runtime.Backend;
import cucumber.runtime.ClassFinder;
import cucumber.runtime.CucumberException;
import cucumber.runtime.DuplicateStepDefinitionException;
import cucumber.runtime.Glue;
import cucumber.runtime.HookDefinition;
import cucumber.runtime.StepDefinition;
import cucumber.runtime.UnreportedStepExecutor;
import cucumber.runtime.Utils;
import cucumber.runtime.arquillian.api.Lambda;
import cucumber.runtime.arquillian.container.ContextualObjectFactoryBase;
import cucumber.runtime.arquillian.container.CukeSpaceCDIObjectFactory;
import cucumber.runtime.arquillian.lifecycle.CucumberLifecycle;
import cucumber.runtime.java.JavaBackend;
import cucumber.runtime.java.StepDefAnnotation;
import cucumber.runtime.snippets.FunctionNameGenerator;
import cucumber.runtime.snippets.Snippet;
import cucumber.runtime.snippets.SnippetGenerator;
import gherkin.formatter.model.Step;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import static cucumber.runtime.arquillian.shared.ClassLoaders.load;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

// patched to use the resource loader defined by this extension
// the best would probably to update cucumber-core to handle
// completely listed feature/steps (glue) classes/resources
public class ArquillianBackend extends JavaBackend implements Backend {
    protected enum GlueType {
        JAVA, SCALA, UNKNOWN
    }

    private SnippetGenerator snippetGenerator;
    private final Collection> glues = new ArrayList>();
    private Glue glue;
    private GlueType glueType = GlueType.UNKNOWN;
    private ObjectFactory objectFactory;
    private Class testClass;

    public ArquillianBackend() { // no-op constructor but we need to be JavaBackend for java8 integration
        super(null, new ClassFinder() {
            private final ClassLoader loader = Thread.currentThread().getContextClassLoader();

            @Override
            public  Collection> getDescendants(final Class parentType, final String packageName) {
                return Collections.emptyList();
            }

            @Override
            public  Class loadClass(final String s) throws ClassNotFoundException {
                return (Class) loader.loadClass(s);
            }
        });
        this.objectFactory = defaultObjectFactory(null, null);
    }

    public ArquillianBackend(final Collection> classes, final Class clazz, final Object testInstance, final String objectFactory) {
        this();
        this.glues.addAll(classes);
        this.testClass = clazz;
        try {
            this.objectFactory = objectFactory == null ?
                    defaultObjectFactory(clazz, testInstance) :
                    wrapObjectFactory(clazz, testInstance, "cdi".equalsIgnoreCase(objectFactory.trim()) ?
                            new CukeSpaceCDIObjectFactory() :
                            ObjectFactory.class.cast(Thread.currentThread().getContextClassLoader()
                                    .loadClass(objectFactory.trim()).getConstructor().newInstance()));
        } catch (final InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException e) {
            throw new IllegalStateException(e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException(e.getCause());
        }
    }

    // ensure test class is used as glue
    private ObjectFactory wrapObjectFactory(final Class clazz, final Object testInstance, final ObjectFactory cast) {
        return new ObjectFactory() {
            @Override
            public void start() {
                cast.start();
            }

            @Override
            public void stop() {
                cast.stop();
            }

            @Override
            public boolean addClass(final Class glueClass) {
                return glueClass == clazz || cast.addClass(glueClass);
            }

            @Override
            public  T getInstance(final Class glueClass) {
                return glueClass == clazz ? glueClass.cast(testInstance) : cast.getInstance(glueClass);
            }
        };
    }

    // plain newInstance()
    private ObjectFactory defaultObjectFactory(final Class clazz, final Object testInstance) {
        return new ContextualObjectFactoryBase() {
            private final Map, Object> instances = new HashMap<>();

            {
                // yes before start cause otherwise we'll duplicate the instance and not behave properly
                instances.put(clazz, testInstance);
            }

            @Override
            public void stop() {
                instances.clear();
            }

            @Override
            public boolean addClass(final Class clazz) {
                return getInstance(clazz) != null;
            }

            @Override
            public  T getInstance(final Class type) {
                T instance = type.cast(instances.get(type));
                if (instance == null) {
                    try {
                        final Constructor constructor = type.getConstructor();
                        instance = constructor.newInstance();
                        instances.put(type, instance);
                        return instance;
                    } catch (final Exception e) {
                        throw new CucumberException(String.format("Failed to instantiate %s", type), e);
                    }
                }
                return instance;
            }
        };
    }

    @Override
    public void loadGlue(final Glue glue, final List gluePaths) {
        super.loadGlue(glue, Collections.emptyList());
        this.glue = glue;
        scan(); // dedicated scanning
    }

    private void initInstances() {
        for (final Class glueClass : glues) {
            try {
                initLambda(CucumberLifecycle.enrich(objectFactory.getInstance(glueClass)));
            } catch (final Exception e) {
                throw new IllegalArgumentException("Can't instantiate " + glueClass.getName(), e);
            }
        }
        if (testClass != null) {
            initLambda(CucumberLifecycle.enrich(objectFactory.getInstance(testClass)));
        }
    }

    private void initLambda(final Object instance) {
        beforeCreate();
        try {
            if (Lambda.class.isInstance(instance)) {
                Lambda.class.cast(instance).define();
            }
        } finally {
            afterCreate();
        }
    }

    private void scan() {
        for (final Collection> list : asList(glues, singletonList(testClass))) {
            for (final Class clazz : list) {
                if (clazz == null) { // testclass can be null
                    continue;
                }

                if (readFromJava(clazz)) {
                    glueType = GlueType.JAVA;
                }
                if (readFromScalaDsl(objectFactory.getInstance(clazz)) && glueType != GlueType.JAVA) {
                    glueType = GlueType.SCALA;
                }
            }
            if (glueType != GlueType.UNKNOWN) {
                break;
            }
        }
    }

    private boolean readFromJava(final Class clazz) {
        boolean found = false;
        for (final Method method : clazz.getMethods()) {
            for (final Class cucumberAnnotationClass : CucumberLifecycle.cucumberAnnotations()) {
                final Annotation annotation = method.getAnnotation(cucumberAnnotationClass);
                if (annotation != null) {
                    if (isHookAnnotation(annotation)) {
                        addHook(annotation, method, objectFactory.getInstance(clazz));
                        found = true;
                    } else if (isStepdefAnnotation(annotation)) {
                        addStepDefinition(annotation, method, objectFactory.getInstance(clazz));
                        found = true;
                    }
                }
            }
        }
        return found;
    }

    private boolean readFromScalaDsl(final Object instance) {
        try {
            // ensure scala module is activated
            load("cucumber.api.scala.ScalaDsl");

            // read info directly {@see cucumber.api.scala.ScalaDsl}
            final Class clazz = instance.getClass();

            final Collection stepDefinitions = readField(clazz, "stepDefinitions", instance, StepDefinition.class);
            for (final StepDefinition sd : stepDefinitions) {
                glue.addStepDefinition(StepDefinition.class.cast(sd));
            }

            final Collection beforeHooks = readField(clazz, "beforeHooks", instance, HookDefinition.class);
            for (final HookDefinition sd : beforeHooks) {
                glue.addBeforeHook(HookDefinition.class.cast(sd));
            }

            final Collection afterHooks = readField(clazz, "afterHooks", instance, HookDefinition.class);
            for (final HookDefinition sd : afterHooks) {
                glue.addAfterHook(HookDefinition.class.cast(sd));
            }

            return stepDefinitions.size() + beforeHooks.size() + afterHooks.size() > 0;
        } catch (final Exception e) {
            return false;
        }
    }

    private boolean isHookAnnotation(final Annotation annotation) {
        final Class annotationClass = annotation.annotationType();
        return annotationClass.equals(Before.class) || annotationClass.equals(After.class);
    }

    private boolean isStepdefAnnotation(final Annotation annotation) {
        final Class annotationClass = annotation.annotationType();
        return annotationClass.getAnnotation(StepDefAnnotation.class) != null;
    }

    private void addStepDefinition(final Annotation annotation, final Method method, final Object instance) {
        try {
            glue.addStepDefinition(new ArquillianStepDefinition(method, pattern(annotation), timeout(annotation), instance));
        } catch (DuplicateStepDefinitionException e) {
            throw e;
        } catch (Throwable e) {
            throw new CucumberException(e);
        }
    }

    private Pattern pattern(final Annotation annotation) throws Throwable {
        final Method regexpMethod = annotation.getClass().getMethod("value");
        final String regexpString = (String) Utils.invoke(annotation, regexpMethod, 0);
        return Pattern.compile(regexpString);
    }

    private long timeout(final Annotation annotation) throws Throwable {
        final Method regexpMethod = annotation.getClass().getMethod("timeout");
        return (Long) Utils.invoke(annotation, regexpMethod, 0);
    }

    private void addHook(final Annotation annotation, final Method method, final Object instance) {
        if (annotation.annotationType().equals(Before.class)) {
            final String[] tagExpressions = ((Before) annotation).value();
            final long timeout = ((Before) annotation).timeout();
            glue.addBeforeHook(new ArquillianHookDefinition(method, tagExpressions, ((Before) annotation).order(), timeout, instance));
        } else {
            final String[] tagExpressions = ((After) annotation).value();
            final long timeout = ((After) annotation).timeout();
            glue.addAfterHook(new ArquillianHookDefinition(method, tagExpressions, ((After) annotation).order(), timeout, instance));
        }
    }

    @Override
    public void setUnreportedStepExecutor(UnreportedStepExecutor executor) {
        //Not used here yet
    }

    public void beforeCreate() {
        INSTANCE.set(this);
    }

    public void afterCreate() {
        INSTANCE.remove();
    }

    @Override
    public void buildWorld() {
        objectFactory.start();
        initInstances();
    }

    @Override
    public void disposeWorld() {
        objectFactory.stop();
    }

    @Override
    public String getSnippet(final Step step, final FunctionNameGenerator nameGenerator) {
        if (snippetGenerator == null) { // leaving a double if ATM if we need to add other language support
            if (GlueType.SCALA.equals(glueType)) {
                try {
                    snippetGenerator = new SnippetGenerator(Snippet.class.cast(load("cucumber.runtime.scala.ScalaSnippetGenerator").newInstance()));
                } catch (final Exception e) {
                    // let use the default
                }
            }
        }

        if (snippetGenerator == null) { // JAVA is the default too
            snippetGenerator = new SnippetGenerator(new ArquillianSnippet());
        }

        return snippetGenerator.getSnippet(step, nameGenerator);
    }

    private static  Collection readField(final Class clazz, final String field, final Object instance, final Class cast) throws Exception {
        final Field f = clazz.getDeclaredField(field);
        f.setAccessible(true);
        final Object o = f.get(instance);

        final Class arrayBuffer = load("scala.collection.mutable.ArrayBuffer");
        if (arrayBuffer.isInstance(o)) {
            final Object[] array = Object[].class.cast(arrayBuffer.getDeclaredMethod("array").invoke(o));
            final Collection result = new ArrayList(array.length);
            for (final Object i : array) {
                if (cast.isInstance(i)) {
                    result.add(cast.cast(i));
                }
            }
            return result;
        }

        throw new IllegalArgumentException("expected an ArrayBuffer and got " + o);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy