com.googlecode.fightinglayoutbugs.helpers.TestHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.fightinglayoutbugs.helpers.TestHelper.java

Source

/*
 * Copyright 2009-2012 Michael Tamm
 *
 * 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 com.googlecode.fightinglayoutbugs.helpers;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import junit.framework.Assert;
import org.apache.commons.io.FileUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.SelfDescribing;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsInstanceOf;
import org.hamcrest.core.IsNull;
import org.hamcrest.core.IsSame;
import org.hamcrest.number.OrderingComparison;
import org.mockito.Mockito;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class TestHelper extends Mockito {

    public static void fail(String message) {
        Assert.fail(message);
    }

    public static void assertThat(boolean shouldBeTrue) {
        if (!shouldBeTrue) {
            fail();
        }
    }

    public static <T> void assertThat(T actual, Matcher<T> matcher) {
        if (!matcher.matches(actual)) {
            fail();
        }
    }

    public static class AndMatcher<T> extends BaseMatcher<T> {

        private final List<Matcher<? extends T>> _matchers;

        public AndMatcher(Matcher<T> matcher) {
            _matchers = new LinkedList<Matcher<? extends T>>();
            _matchers.add(matcher);
        }

        @SuppressWarnings({ "ReturnOfThis" })
        public AndMatcher<T> and(Matcher<? extends T> matcher) {
            _matchers.add(matcher);
            return this;
        }

        @SuppressWarnings({ "MethodWithMultipleReturnPoints" })
        public boolean matches(Object item) {
            for (Matcher<? extends T> m : _matchers) {
                if (!m.matches(item)) {
                    return false;
                }
            }
            return true;
        }

        public void describeTo(Description description) {
            for (Iterator<Matcher<? extends T>> i = _matchers.iterator(); i.hasNext();) {
                final Matcher<? extends T> m = i.next();
                m.describeTo(description);
                if (i.hasNext()) {
                    description.appendText(" and ");
                }
            }
        }
    }

    public static <T> AndMatcher<T> both(Matcher<T> matcher) {
        return new AndMatcher<T>(matcher);
    }

    public static class OrMatcher<T> extends BaseMatcher<T> {

        private final List<Matcher<? extends T>> _matchers;

        public OrMatcher(Matcher<T> matcher) {
            _matchers = new LinkedList<Matcher<? extends T>>();
            _matchers.add(matcher);
        }

        @SuppressWarnings({ "ReturnOfThis" })
        public OrMatcher<T> or(T value) {
            _matchers.add(isEqualTo(value));
            return this;
        }

        @SuppressWarnings({ "ReturnOfThis" })
        public OrMatcher<T> or(Matcher<T> matcher) {
            _matchers.add(matcher);
            return this;
        }

        @SuppressWarnings({ "MethodWithMultipleReturnPoints" })
        public boolean matches(Object item) {
            for (Matcher<? extends T> m : _matchers) {
                if (m.matches(item)) {
                    return true;
                }
            }
            return false;
        }

        public void describeTo(Description description) {
            for (Iterator<Matcher<? extends T>> i = _matchers.iterator(); i.hasNext();) {
                final Matcher<? extends T> m = i.next();
                m.describeTo(description);
                if (i.hasNext()) {
                    description.appendText(" or ");
                }
            }
        }
    }

    public static <T> OrMatcher<T> either(Matcher<T> matcher) {
        return new OrMatcher<T>(matcher);
    }

    public static <T> OrMatcher<T> isEither(T value) {
        return new OrMatcher<T>(isEqualTo(value));
    }

    public static class NorMatcher<T> extends BaseMatcher<T> {

        private final List<Matcher<? extends T>> _matchers;

        public NorMatcher(Matcher<? extends T> matcher) {
            _matchers = new LinkedList<Matcher<? extends T>>();
            _matchers.add(matcher);
        }

        @SuppressWarnings({ "ReturnOfThis" })
        public NorMatcher<T> nor(T value) {
            _matchers.add(isEqualTo(value));
            return this;
        }

        @SuppressWarnings({ "ReturnOfThis" })
        public NorMatcher<T> nor(Matcher<? extends T> matcher) {
            _matchers.add(matcher);
            return this;
        }

        @SuppressWarnings({ "MethodWithMultipleReturnPoints" })
        public boolean matches(Object item) {
            for (Matcher<? extends T> m : _matchers) {
                if (m.matches(item)) {
                    return false;
                }
            }
            return true;
        }

        public void describeTo(Description description) {
            String separator = "neither ";
            for (Matcher<? extends T> m : _matchers) {
                description.appendText(separator);
                m.describeTo(description);
                separator = " nor ";
            }
        }
    }

    public static <T> NorMatcher<T> neither(Matcher<T> matcher) {
        return new NorMatcher<T>(matcher);
    }

    /**
     * Handles {@code null}, numbers, collections, and arrays.
     */
    public static Matcher<Object> is(final Object expected, final Object... moreExpectedValues) {
        return new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object o) {
                boolean matches;
                if (o == null) {
                    matches = (moreExpectedValues.length == 0 && expected == null);
                } else if (o instanceof Number) {
                    if (moreExpectedValues.length > 0) {
                        matches = false;
                    } else {
                        if (expected instanceof Number) {
                            if (expected instanceof Long && o instanceof Integer) {
                                matches = (((Long) expected) == ((Integer) o).longValue());
                            } else if (expected instanceof Integer && o instanceof Long) {
                                matches = (((Integer) expected).longValue() == ((Long) o));
                            } else {
                                matches = o.equals(expected);
                            }
                        } else {
                            matches = false;
                        }
                    }
                } else if (o instanceof Collection) {
                    if (moreExpectedValues.length > 0) {
                        if (o instanceof Set) {
                            Set<?> expectedSet = Sets.union(TestHelper.asSet(expected),
                                    TestHelper.asSet(moreExpectedValues));
                            matches = expectedSet.equals(o);
                        } else if (o instanceof List) {
                            List<?> expectedList = Lists.asList(expected, moreExpectedValues);
                            matches = expectedList.equals(o);
                        } else {
                            throw new RuntimeException(
                                    "Don't know how to match a " + o.getClass().getName() + " instance.");
                        }
                    } else if (expected == null) {
                        throw new RuntimeException(
                                "Ambigious matcher: Use either isNull() or isEqualTo(Collections.singleton(null))");
                    } else if (expected instanceof Collection) {
                        matches = expected.equals(o);
                    } else {
                        matches = (((Collection) o).size() == 1
                                && expected.equals(((Collection) o).iterator().next()));
                    }
                } else if (o.getClass().isArray()) {
                    final Object expectedArray;
                    if (moreExpectedValues.length > 0) {
                        final Object[] a = new Object[moreExpectedValues.length + 1];
                        a[0] = expected;
                        System.arraycopy(moreExpectedValues, 0, a, 1, moreExpectedValues.length);
                        expectedArray = a;
                    } else if (expected != null && !expected.getClass().isArray()) {
                        expectedArray = new Object[] { expected };
                    } else {
                        expectedArray = expected;
                    }
                    matches = new IsEqual<Object>(expectedArray).matches(o);
                } else {
                    matches = (moreExpectedValues.length == 0 && o.equals(expected));
                }
                return matches;
            }

            @Override
            public void describeTo(Description description) {
                if (moreExpectedValues.length == 0) {
                    if (expected instanceof SelfDescribing) {
                        ((SelfDescribing) expected).describeTo(description);
                    } else {
                        description.appendText(StringHelper.asString(expected));
                    }
                } else {
                    final List<Object> temp = new ArrayList<Object>(moreExpectedValues.length + 1);
                    temp.add(expected);
                    temp.addAll(Arrays.asList(moreExpectedValues));
                    description.appendText(Joiner.on(", ").join(temp));
                }
            }
        };
    }

    /**
     * Is the value equal to another value, as tested by the
     * {@link Object#equals(Object)} method with special handling for arrays.
     */
    public static <T> Matcher<T> isEqualTo(T operand) {
        return IsEqual.equalTo(operand);
    }

    /**
     * Is the value an instance of a particular class?
     */
    public static Matcher<Object> isInstanceOf(Class<?> clazz) {
        return IsInstanceOf.instanceOf(clazz);
    }

    /**
     * Evaluates to {@code true} only if the matched value is the same instance as the given object.
     */
    public static <T> Matcher<T> isSameInstanceAs(T object) {
        return IsSame.sameInstance(object);
    }

    /**
     * Matches if the matched value is {@code null}.
     */
    public static Matcher<Object> isNull() {
        return IsNull.nullValue();
    }

    /**
     * Matches if the matched value is not {@code null}.
     */
    public static Matcher<Object> isNotNull() {
        return IsNull.notNullValue();
    }

    /**
     * This matcher always evaluates to {@code true}, use it like this:<pre>
     *     assertThat(foo(), doesNotThrowAnException());
     * </pre>
     */
    public static Matcher<Object> doesNotThrowAnException() {
        return new BaseMatcher<Object>() {
            public boolean matches(Object item) {
                return true;
            }

            public void describeTo(Description description) {
                description.appendText("does not throw an exception");
            }
        };
    }

    /**
     * Works for strings, arrays, collections, and maps.
     */
    public static Matcher<Object> isEmpty() {
        return new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object o) {
                final boolean result;
                if (o == null) {
                    result = false;
                } else if (o instanceof CharSequence || o instanceof StringWriter) {
                    final String s = o.toString();
                    result = (s.length() == 0);
                } else if (o instanceof Collection) {
                    result = ((Collection) o).isEmpty();
                } else if (o instanceof Map) {
                    result = ((Map) o).isEmpty();
                } else if (o.getClass().isArray()) {
                    result = (Array.getLength(o) == 0);
                } else {
                    throw new RuntimeException("Don't know how to handle object of type " + o.getClass().getName());
                }
                return result;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("empty");
            }
        };
    }

    public static <T extends Comparable> AndMatcher isGreaterThan(T value) {
        return new AndMatcher<T>(OrderingComparison.greaterThan(value));
    }

    public static <T extends Comparable<T>> AndMatcher isGreaterThanOrEqualTo(T value) {
        return new AndMatcher<T>(OrderingComparison.greaterThanOrEqualTo(value));
    }

    public static <T extends Comparable<T>> AndMatcher isLessThan(T value) {
        return new AndMatcher<T>(OrderingComparison.lessThan(value));
    }

    public static <T extends Comparable<T>> AndMatcher isLessThanOrEqualTo(T value) {
        return new AndMatcher<T>(OrderingComparison.lessThanOrEqualTo(value));
    }

    private static void fail() {
        String assertionErrorMessage = "";
        // Try to extract assertion error message from the source file, from which assertThat has been called ...
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        int i = 0;
        while (!"assertThat".equals(stackTrace[i].getMethodName())) {
            ++i;
        }
        ++i;
        final File javaFile = findSourceFileFor(stackTrace[i]);
        if (javaFile != null) {
            final int lineNumber = stackTrace[i].getLineNumber();
            try {
                final List<String> lines = FileUtils.readLines(javaFile, "UTF-8");
                final String line = lines.get(lineNumber - 1).trim();
                if (line.startsWith("assertThat(")) {
                    assertionErrorMessage = line.substring("assertThat(".length());
                    if (assertionErrorMessage.endsWith(");")) {
                        assertionErrorMessage = assertionErrorMessage.substring(0,
                                assertionErrorMessage.length() - ");".length());
                    } else {
                        assertionErrorMessage += " ...";
                    }
                }
            } catch (IOException ignored) {
            }
        }
        AssertionError e = new AssertionError(assertionErrorMessage);
        e.setStackTrace(Arrays.copyOfRange(stackTrace, i, stackTrace.length));
        throw e;
    }

    private static File findSourceFileFor(StackTraceElement stackTraceElement) {
        final String className = stackTraceElement.getClassName();
        final int i = className.lastIndexOf('.');
        final String packageDir = (i == -1 ? "" : "/" + className.substring(0, i).replace('.', '/'));
        final File workDir = new File(new File("dummy").getAbsolutePath()).getParentFile();
        // Maven 2 directory layout ...
        final String testSourcesDir = "src/test/java";
        final String sourceFileName = stackTraceElement.getFileName();
        File sourceFile = new File(testSourcesDir + packageDir, sourceFileName);
        if (!sourceFile.exists()) {
            System.err.println("Could not find " + sourceFile.getAbsolutePath() + " (current work dir: "
                    + workDir.getAbsolutePath() + ").");
            sourceFile = null;
        }
        return sourceFile;
    }

    /**
     * Repetitively runs the given <code>runnableAssert</code> until it
     * succeeds without throwing an exception or error or until the
     * given <code>timeout</code> is reached.
     *
     * @throws TimeoutException if the given <code>runnableAssert</code> could not be executed successfully in the given time
     */
    public static void waitFor(long timeout, TimeUnit timeoutUnit, RunnableAssert runnableAssert) {
        boolean success = false;
        final long timeoutReached = System.currentTimeMillis() + timeoutUnit.toMillis(timeout);
        do {
            try {
                runnableAssert.run();
                success = true;
            } catch (Exception e) {
                if (System.currentTimeMillis() > timeoutReached) {
                    AssertionError assertionError = new AssertionError(
                            "Timeout while waiting for: " + runnableAssert + ".");
                    assertionError.initCause(e);
                    throw assertionError;
                }
                try {
                    Thread.sleep(25);
                } catch (InterruptedException e2) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Got interrupted while waiting for: " + runnableAssert + ".", e2);
                }
            }
        } while (!success);
    }

    /**
     * Returns a {@link List} containing the given <code>objects</code>.
     */
    public static <T> List<T> asList(T... items) {
        return new ArrayWrapper<T>(items);
    }

    /**
     * Returns a {@link Set} containing the given <code>objects</code>.
     */
    public static <T> Set<T> asSet(T... objects) {
        final Set<T> result;
        result = new LinkedHashSet<T>();
        result.addAll(new ArrayWrapper<T>(objects));
        return result;
    }

    /**
     * Returns a {@link Map} with the mappings <code>keysAndValues[0] => keysAndValues[1],
     * keysAndValues[2] => keysAndValues[3], ...</code>.
     */
    public static <K, V> Map<K, V> asMap(Object... keysAndValues) {
        final Map<K, V> result = new LinkedHashMap<K, V>();
        final int n = keysAndValues.length;
        if (n % 2 == 1) {
            throw new IllegalArgumentException("You must provide an even number of arguments.");
        }
        for (int i = 0; i < n; i += 2) {
            result.put((K) keysAndValues[i], (V) keysAndValues[i + 1]);
        }
        return result;
    }

    /**
     * This class is similar to the class which is returned by {@link Arrays#asList}
     * but does not clone the wrapped array if {@link #toArray} is called.
     */
    private static class ArrayWrapper<E> extends AbstractList<E> {
        private final E[] _wrappedArray;

        private ArrayWrapper(E[] arrayToWrap) {
            _wrappedArray = arrayToWrap;
        }

        @Override
        public E get(int index) {
            return _wrappedArray[index];
        }

        @Override
        public int size() {
            return _wrappedArray.length;
        }

        /**
         * Returns the wrapped array.
         */
        @Override
        public Object[] toArray() {
            return _wrappedArray;
        }
    }

    private TestHelper() {
    }
}