de.micromata.genome.tpsb.CommonTestBuilder.java Source code

Java tutorial

Introduction

Here is the source code for de.micromata.genome.tpsb.CommonTestBuilder.java

Source

//
// Copyright (C) 2010-2016 Micromata GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package de.micromata.genome.tpsb;

import de.micromata.genome.tpsb.annotations.TpsbBuilder;
import de.micromata.genome.tpsb.annotations.TpsbIgnore;
import de.micromata.genome.util.bean.PrivateBeanUtils;
import de.micromata.genome.util.runtime.AssertUtils;
import de.micromata.genome.util.runtime.CallableX;
import de.micromata.genome.util.runtime.GroovyUtils;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.groovy.control.CompilationFailedException;

/**
 * Common testbuilder with testContext.
 * 
 * @author roger
 * 
 * @param <T> the type of the testbuilder
 */
@TpsbBuilder
public class CommonTestBuilder<T extends CommonTestBuilder<?>> implements TestBuilder<T> {
    private static final Logger LOG = Logger.getLogger(CommonTestBuilder.class);
    /**
     * Backup of all parameter of Testbuilder.
     */
    protected Map<String, Object> testContext = new HashMap<String, Object>();

    /**
     * The Comment listener.
     */
    protected TpsbCommentListener commentListener = new Log4JCommentListener();

    /**
     * A map of saved TestBuilders.
     * 
     * TODO RK THIS IS NOT THREADSAFE. Running test in multiple threads, will fail if static.
     */
    protected Map<String, CommonTestBuilder<?>> testContextMap = new HashMap<String, CommonTestBuilder<?>>();

    /**
     * In case an exception this interceptor is active
     */
    protected TpsbExpectExInterceptor<T> expectedExInterceptor = null;

    /**
     * Instantiates a new Common test builder.
     */
    public CommonTestBuilder() {

    }

    /**
     * Instantiates a new Common test builder.
     * 
     * @param other the other
     */
    public CommonTestBuilder(CommonTestBuilder<?> other) {
        this.testContext = other.testContext;
        PrivateBeanUtils.fetchAllNonStaticFields(other.testContext, other.getClass(), other);
        PrivateBeanUtils.populate(this, testContext);
    }

    /**
     * Copy vars to other builder.
     * 
     * @param target the target
     */
    @TpsbIgnore
    private void copyVarsToOtherBuilder(TestBuilder<?> target) {
        PrivateBeanUtils.fetchAllNonStaticFields(testContext, getClass(), this);
        target.getTestContext().putAll(this.testContext);
        populate(target, testContext);

    }

    private static void populate(Object bean, Map<String, Object> properties) {
        for (Map.Entry<String, Object> me : properties.entrySet()) {
            Field f = PrivateBeanUtils.findField(bean, me.getKey());
            if (f == null) {
                continue;
            }
            try {
                PrivateBeanUtils.writeField(bean, f, me.getValue());
            } catch (Exception ex) {
                LOG.warn("Cannot copy var: " + f.getName() + " to " + bean.getClass().getName() + ": "
                        + ex.getMessage(), ex);
            }
        }
    }

    /**
     * Create a new TestBuilder of given Class.
     * 
     * After creating the new TestBuilder, it copies the existant testcontext to the new TestBuilder
     * 
     * Note: the class must not be generic.
     * 
     * @param clazz Class of the new created builder.
     */
    @Override
    public <X> X createBuilder(Class<X> clazz) {
        try {
            // TpsbTestBase.watcher.wrap()
            X newBuilder = clazz.newInstance();
            if (newBuilder instanceof TestBuilder) {
                copyVarsToOtherBuilder((TestBuilder<?>) newBuilder);
                if (newBuilder instanceof CommonTestBuilder) {
                    CommonTestBuilder<?> ctm = (CommonTestBuilder<?>) newBuilder;
                    ctm.expectedExInterceptor = null;
                }
            }
            return newBuilder;
        } catch (Exception ex) {
            throw new RuntimeException("Cannot create builder class: " + clazz + "; " + ex.getMessage(), ex);
        }
    }

    /**
     * Gets builder.
     * 
     * @return the builder
     */
    @SuppressWarnings("unchecked")
    @TpsbIgnore
    final protected T getBuilder() {
        return (T) this;
    }

    /**
     * Sets a variable inside the testcontext.
     * 
     * if in the current builder a Field with same name and assignable type, it will be set too.
     * 
     * @param varName the variable name of context variable.
     * @param value value
     * @return builder
     */
    @Override
    public T setTestContextVar(String varName, Object value) {
        testContext.put(varName, value);
        Field f = PrivateBeanUtils.findField(getClass(), varName);
        if (f != null) {
            PrivateBeanUtils.writeField(this, f, value);
        }
        return getBuilder();
    }

    /**
     * Setzt den ausgewerteten Wert der expression als Variable im TestKontext
     * 
     * @param varName Name of the variable
     * @param expression A Groovy expression.
     * @return context var expression
     */
    public T setContextVarExpression(String varName, String expression) {
        copyThisToTestContext();
        Object ret = evaluate(testContext, expression, this);
        setTestContextVar(varName, ret);
        return getBuilder();
    }

    /**
     * Check if given context variable is equal to expectedValue
     * 
     * @param varName Name of the variable
     * @param expectedValue expected value
     * @return t
     */
    public T validateTestContextVar(String varName, Object expectedValue) {
        copyThisToTestContext();
        Object realVal = testContext.get(varName);
        if (ObjectUtils.equals(realVal, expectedValue) == false) {
            failImpl("Variable " + varName + " is not excected value: " + expectedValue + " but " + realVal);
        }
        return getBuilder();

    }

    /**
     * Store the current testbuilder under given name
     * 
     * @param name variable name
     * @return builder t
     */
    public T storeTestBuilder(String name) {
        testContextMap.put(name, this);
        return getBuilder();
    }

    /**
     * return to the named previously stored TestBuilder and remove the testbuilder from the storage.
     * 
     * @param name of the builder in the test context.
     * @param exprectedType type of the test builder.
     * @param <X> the type of the testbuilder
     * @return builder x
     */
    public <X extends CommonTestBuilder<?>> X returnToStoredTestBuilder(String name, Class<X> exprectedType) {
        X ret = switchToStoredTestBuilder(name, exprectedType);
        testContext.remove(name);
        return ret;
    }

    /**
     * Stores the current testbuilder under the fully qualified class name.
     * 
     * @return builder
     */
    public T storeCurrentTestBuilder() {
        return storeTestBuilder(this.getClass().getName());
    }

    /**
     * Restore previous storeCurrentTestBuilder on given class.
     * 
     * @param <X> the generic type
     * @param testBuilderClass the test builder class
     * @return the x
     */
    public <X extends CommonTestBuilder<?>> X returnToPreviousTestBuilder(Class<X> testBuilderClass) {
        return returnToStoredTestBuilder(testBuilderClass.getName(), testBuilderClass);
    }

    /**
     * switch to previously stored Testbuilder, but does not delete it from storage.
     * 
     * @param name the name
     * @param exprectedType the exprected type
     * @param <X> the type of the testbuilder
     * @return x
     */
    public <X extends CommonTestBuilder<?>> X switchToStoredTestBuilder(String name, Class<X> exprectedType) {
        CommonTestBuilder<?> ret = testContextMap.get(name);
        if (ret == null) {
            fail("There is no stored TestBuilder with name: " + name);
        }
        return (X) ret;
    }

    @Override
    @TpsbIgnore
    public Object getTestContextVar(String varName) {
        return testContext.get(varName);
    }

    @Override
    @TpsbIgnore
    public Map<String, Object> getTestContext() {
        copyThisToTestContext();
        return testContext;
    }

    /**
     * Clear the context state of the bilder.
     * 
     * @return t
     */
    public T clearTestContext() {
        testContext.clear();
        return getBuilder();
    }

    /**
     * Copy test context to.
     * 
     * @param target the target
     */
    @TpsbIgnore
    protected void copyTestContextTo(CommonTestBuilder<?> target) {
        target.testContext.putAll(testContext);
        PrivateBeanUtils.populate(target, target.testContext);
    }

    /**
     * Copy this to test context.
     */
    @TpsbIgnore
    protected void copyThisToTestContext() {
        testContext.putAll(PrivateBeanUtils.getAllFields(this, 0, Modifier.STATIC | Modifier.TRANSIENT));
        testContext.put("builder", this);
    }

    /**
     * Add a comment
     * 
     * @param text the text
     * @return t
     */
    public T addComment(String text) {
        commentListener.addCommentText(this, text);
        return getBuilder();
    }

    /**
     * Add a comment expression
     * 
     * @param expression a groovy expression.
     * @return t
     */
    public T addCommentExpression(String expression) {
        commentListener.addCommentExpression(this, expression);
        return getBuilder();
    }

    /**
     * validate the boolean expression. If not true, fail with message
     * 
     * @param expression boolean
     * @param message Message to fail
     * @return t
     */
    public T validateTrue(boolean expression, String message) {
        if (expression == true) {
            return getBuilder();
        }
        return failImpl("validateTrue failed: " + message);
    }

    /**
     * validate the boolean expression. If not false, fail with message.
     * 
     * @param expression boolean
     * @param message Message to fail
     * @return failimpl when an error happened or the builder itself
     */
    public T validateFalse(boolean expression, String message) {
        if (expression == false) {
            return getBuilder();
        }

        return failImpl("validateFalse failed: " + message);
    }

    /**
     * Validates that both values are equal.
     * 
     * @param expected what to expect
     * @param given what to check if it matches the expected
     * @return failimpl when it does not match or the builder
     */
    public T validateEquals(Object expected, Object given) {
        if (ObjectUtils.equals(given, expected) == true) {
            return getBuilder();
        }
        return failImpl("Expect: " + expected + "; given: " + given);
    }

    /**
     * Validates that both values are not equal.
     *
     * @param expected what to expect
     * @param given what to check if it matches the expected
     * @return failimpl when it does not match or the builder
     */
    public T validateNotEquals(Object expected, Object given) {
        if (ObjectUtils.equals(given, expected) == false) {
            return getBuilder();
        }
        return failImpl("Expect: " + expected + "; given: " + given);
    }

    @TpsbIgnore
    public T failMissingException(final Class<? extends Throwable> expected) {
        throw new AssertionMissingException(expected, this);
    }

    /**
     * Run next statement and validate if expected exception is thrown.
     * 
     * The next statement must return the this in the method.
     * 
     * The catched expected exception will be stored in the testContext with the name "expectedException"
     * 
     * @param expectEx expected exception.
     * @param callable callback
     * @param <EX> the type of the exception
     * @return t
     */
    @TpsbIgnore
    public <EX extends Throwable> T runWithExpectedException(final Class<EX> expectEx, CallableX<T, EX> callable) {
        try {
            callable.call();
            fail("Missing expected exception: " + expectEx.getCanonicalName());
        } catch (Throwable ex) { // NOSONAR "Illegal Catch" framework
            if (expectEx.isAssignableFrom(ex.getClass()) == false) {
                fail("expected exception: " + expectEx.getCanonicalName() + " but got: "
                        + ex.getClass().getCanonicalName() + "; " + ex.getMessage());
            }
        }
        return getBuilder();
    }

    /**
     * Calling next test step expect to throw an exception
     * 
     * @param exceptionClass the exception class
     * @return t
     */
    @SuppressWarnings("unchecked")
    public T validateNextException(Class<? extends Throwable> exceptionClass) {
        if (expectedExInterceptor == null) {
            expectedExInterceptor = new TpsbExpectExInterceptor(getBuilder(), exceptionClass);
        } else {
            expectedExInterceptor.setExpectedException(exceptionClass);
            expectedExInterceptor.setActive(true);
        }
        return expectedExInterceptor.getProxy();
    }

    /**
     * validate groovy expression.
     * 
     * All nonstatics will be copied into testContext
     * 
     * @param expression the expression
     * @return the t
     */
    public T validateExpression(String expression) {
        copyThisToTestContext();
        Object ret = evaluate(testContext, expression, this);
        if (ret == null) {
            return failImpl("Expression returned null: " + expression);
        }
        if (ret instanceof Boolean && ((Boolean) ret).booleanValue() == false) {
            return failImpl("Expression returned false: " + expression);
        }
        return getBuilder();
    }

    /**
     * execute a groovy expression.
     * 
     * All nonstatics will be copied into testContext.
     * 
     * The result will be stored int he testContet as tpsbExpressionResult.
     * 
     * @param expression the expression
     * @return t
     */
    public T executeExpression(String expression) {
        copyThisToTestContext();
        Object ret = evaluate(testContext, expression, this);
        testContext.put("tpsbExpressionResult", ret);
        return getBuilder();
    }

    /**
     * evaluate the given expression with the test context.
     * 
     * @param expression the expression
     * @return object
     */
    @TpsbIgnore
    public Object eval(String expression) {
        copyThisToTestContext();
        return evaluate(testContext, expression, getClass());
    }

    /**
     * Evaluate object.
     * 
     * @param context the context
     * @param expression the expression
     * @return the object
     */
    @TpsbIgnore
    public static Object evaluate(Map<String, Object> context, String expression) {
        return evaluate(context, expression, null);

    }

    /**
     * Evaluate object.
     * 
     * @param context the context
     * @param expression the expression
     * @param deriveFrom the derive from
     * @return the object
     */
    @TpsbIgnore
    public static Object evaluate(Map<String, Object> context, String expression, Object deriveFrom) {
        Binding b = new Binding();
        for (Map.Entry<String, Object> me : context.entrySet()) {
            b.setVariable(me.getKey(), me.getValue());
        }
        String text = expression;

        GroovyShell sh = new GroovyShell(b);
        try {
            Object ret = GroovyUtils.evaluate(sh, text, "testbuilderexpr.groovy");
            return ret;
        } catch (CompilationFailedException e) {
            throw new RuntimeException("Invalid expression: " + expression + "; " + e.getMessage(), e);
        }

    }

    /**
     * Validate expression.
     * 
     * @param bean the bean
     * @param expression the expression
     * @return the t
     */
    public T validateExpression(final Object bean, final String expression) {
        testContext.put("bean", bean);
        return validateExpression(expression);
    }

    /**
     * Just fail the test
     * 
     * @param message to printed as fail
     * @return t
     */
    public T fail(String message) {
        return failImpl("fail: " + message);
    }

    /**
     * Just fail the test with given exception.
     * 
     * @param ex the ex
     * @return t
     */
    public T fail(AssertionFailedException ex) {
        return failImpl(ex);
    }

    /**
     * Fail impl.
     * 
     * @param message the message
     * @return the t
     */
    protected T failImpl(String message) {
        return failImpl(new AssertionFailedException(message));
    }

    protected T failImpl(AssertionFailedException ex) {
        ex.setTestBuilder(this);
        StackTraceElement el = AssertUtils.getStackAbove(this.getClass());
        if (el != null) {
            String line = StringUtils.trim(AssertUtils.getCodeLine(el));
            ex.setClassName(el.getClassName());
            ex.setMethodName(el.getMethodName());
            ex.setLineNo(el.getLineNumber());
            ex.setCodeLine(line);
        }
        throw ex;
    }

    protected T failImpl(Exception ex) {
        AssertionFailedException nex = new AssertionFailedException(ex);
        nex.setTestBuilder(this);
        StackTraceElement el = AssertUtils.getStackAbove(this.getClass());
        if (el != null) {
            String line = StringUtils.trim(AssertUtils.getCodeLine(el));
            nex.setClassName(el.getClassName());
            nex.setMethodName(el.getMethodName());
            nex.setLineNo(el.getLineNumber());
            nex.setCodeLine(line);
        }
        throw nex;
    }

    /**
     * Dump Builder state information
     * 
     * @return string
     */
    @TpsbIgnore
    public String dumpState() {
        final StringBuilder sb = new StringBuilder();
        dumpState(sb);
        return sb.toString();
    }

    /**
     * Dump builder state to System.out.
     * 
     * @return builder
     */
    public T dumpStateToSystemOut() {
        System.out.println(dumpState());
        return getBuilder();
    }

    /**
     * Dump state.
     * 
     * @param sb the sb
     */
    @TpsbIgnore
    public void dumpState(final StringBuilder sb) {
        sb.append("TestContexte:\n");
        for (Map.Entry<String, Object> me : testContext.entrySet()) {
            sb.append("  ").append(me.getKey()).append(": ")
                    .append(me.getValue() == null ? StringUtils.EMPTY : me.getValue().toString()).append("\n");
        }
    }

    /**
     * Pause the test
     * 
     * @param millis Milliseconds to sleep
     * @return builder t
     */
    public T sleep(long millis) {
        try {
            Thread.sleep(millis);
            return getBuilder();
        } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Gets comment listener.
     * 
     * @return the comment listener
     */
    @TpsbIgnore
    public TpsbCommentListener getCommentListener() {
        return commentListener;
    }

    /**
     * Sets comment listener.
     * 
     * @param commentListener the comment listener
     */
    @TpsbIgnore
    public void setCommentListener(TpsbCommentListener commentListener) {
        this.commentListener = commentListener;
    }

}