com.google.devtools.build.lib.testutil.MoreAsserts.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.testutil.MoreAsserts.java

Source

// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.testutil;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth.assert_;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventCollector;
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.util.Pair;
import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * A helper class for tests providing a simple interface for asserts.
 */
public class MoreAsserts {

    public static <T> void assertEquals(T expected, T actual, Comparator<T> comp) {
        assertThat(comp.compare(expected, actual)).isEqualTo(0);
    }

    public static <T> void assertContentsAnyOrder(Iterable<? extends T> expected, Iterable<? extends T> actual,
            Comparator<? super T> comp) {
        assertThat(actual).hasSize(Iterables.size(expected));
        int i = 0;
        for (T e : expected) {
            for (T a : actual) {
                if (comp.compare(e, a) == 0) {
                    i++;
                }
            }
        }
        assertThat(actual).hasSize(i);
    }

    /**
     * Scans if an instance of given class is strongly reachable from a given
     * object.
     * <p>Runs breadth-first search in object reachability graph to check if
     * an instance of <code>clz</code> can be reached.
     * <strong>Note:</strong> This method can take a long time if analyzed
     * data structure spans across large part of heap and may need a lot of
     * memory.
     *
     * @param start object to start the search from
     * @param clazz class to look for
     */
    public static void assertInstanceOfNotReachable(Object start, final Class<?> clazz) {
        Predicate<Object> p = new Predicate<Object>() {
            @Override
            public boolean apply(Object obj) {
                return clazz.isAssignableFrom(obj.getClass());
            }
        };
        if (isRetained(p, start)) {
            assert_().fail("Found an instance of " + clazz.getCanonicalName() + " reachable from " + start);
        }
    }

    private static final Field NON_STRONG_REF;

    static {
        try {
            NON_STRONG_REF = Reference.class.getDeclaredField("referent");
        } catch (SecurityException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    static final Predicate<Field> ALL_STRONG_REFS = Predicates.equalTo(NON_STRONG_REF);

    private static boolean isRetained(Predicate<Object> predicate, Object start) {
        Map<Object, Object> visited = Maps.newIdentityHashMap();
        visited.put(start, start);
        Queue<Object> toScan = new ArrayDeque<>();
        toScan.add(start);

        while (!toScan.isEmpty()) {
            Object current = toScan.poll();
            if (current.getClass().isArray()) {
                if (current.getClass().getComponentType().isPrimitive()) {
                    continue;
                }

                for (Object ref : (Object[]) current) {
                    if (ref != null) {
                        if (predicate.apply(ref)) {
                            return true;
                        }
                        if (visited.put(ref, ref) == null) {
                            toScan.add(ref);
                        }
                    }
                }
            } else {
                // iterate *all* fields (getFields() returns only accessible ones)
                for (Class<?> clazz = current.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
                    for (Field f : clazz.getDeclaredFields()) {
                        if (f.getType().isPrimitive() || ALL_STRONG_REFS.apply(f)) {
                            continue;
                        }

                        f.setAccessible(true);
                        try {
                            Object ref = f.get(current);
                            if (ref != null) {
                                if (predicate.apply(ref)) {
                                    return true;
                                }
                                if (visited.put(ref, ref) == null) {
                                    toScan.add(ref);
                                }
                            }
                        } catch (IllegalArgumentException | IllegalAccessException e) {
                            throw new IllegalStateException("Error when scanning the heap", e);
                        }
                    }
                }
            }
        }
        return false;
    }

    private static String getClassDescription(Object object) {
        return object == null ? "null" : ("instance of " + object.getClass().getName());
    }

    public static String chattyFormat(String message, Object expected, Object actual) {
        String expectedClass = getClassDescription(expected);
        String actualClass = getClassDescription(actual);

        return Joiner.on('\n').join((message != null) ? ("\n" + message) : "",
                "  expected " + expectedClass + ": <" + expected + ">",
                "  but was " + actualClass + ": <" + actual + ">");
    }

    public static void assertEqualsUnifyingLineEnds(String expected, String actual) {
        if (actual != null) {
            actual = actual.replaceAll(System.getProperty("line.separator"), "\n");
        }
        assertThat(actual).isEqualTo(expected);
    }

    public static void assertContainsWordsWithQuotes(String message, String... strings) {
        for (String string : strings) {
            assertTrue(message + " should contain '" + string + "' (with quotes)",
                    message.contains("'" + string + "'"));
        }
    }

    public static void assertNonZeroExitCode(int exitCode, String stdout, String stderr) {
        if (exitCode == 0) {
            fail("expected non-zero exit code but exit code was 0 and stdout was <" + stdout + "> and stderr was <"
                    + stderr + ">");
        }
    }

    public static void assertZeroExitCode(int exitCode, String stdout, String stderr) {
        assertExitCode(0, exitCode, stdout, stderr);
    }

    public static void assertExitCode(int expectedExitCode, int exitCode, String stdout, String stderr) {
        if (exitCode != expectedExitCode) {
            fail(String.format(
                    "expected exit code <%d> but exit code was <%d> and stdout was <%s> " + "and stderr was <%s>",
                    expectedExitCode, exitCode, stdout, stderr));
        }
    }

    public static void assertStdoutContainsString(String expected, String stdout, String stderr) {
        if (!stdout.contains(expected)) {
            fail("expected stdout to contain string <" + expected + "> but stdout was <" + stdout
                    + "> and stderr was <" + stderr + ">");
        }
    }

    public static void assertStderrContainsString(String expected, String stdout, String stderr) {
        if (!stderr.contains(expected)) {
            fail("expected stderr to contain string <" + expected + "> but stdout was <" + stdout
                    + "> and stderr was <" + stderr + ">");
        }
    }

    public static void assertStdoutContainsRegex(String expectedRegex, String stdout, String stderr) {
        if (!Pattern.compile(expectedRegex).matcher(stdout).find()) {
            fail("expected stdout to contain regex <" + expectedRegex + "> but stdout was <" + stdout
                    + "> and stderr was <" + stderr + ">");
        }
    }

    public static void assertStderrContainsRegex(String expectedRegex, String stdout, String stderr) {
        if (!Pattern.compile(expectedRegex).matcher(stderr).find()) {
            fail("expected stderr to contain regex <" + expectedRegex + "> but stdout was <" + stdout
                    + "> and stderr was <" + stderr + ">");
        }
    }

    public static Set<String> asStringSet(Iterable<?> collection) {
        Set<String> set = Sets.newTreeSet();
        for (Object o : collection) {
            set.add("\"" + o + "\"");
        }
        return set;
    }

    /**
     * If the specified EventCollector contains any events, an informative
     * assertion fails in the context of the specified TestCase.
     */
    public static void assertNoEvents(Iterable<Event> eventCollector) {
        String eventsString = eventsToString(eventCollector);
        assertThat(eventsString).isEmpty();
    }

    /**
     * If the specified EventCollector contains an unexpected number of events, an informative
     * assertion fails in the context of the specified TestCase.
     */
    public static void assertEventCount(int expectedCount, EventCollector eventCollector) {
        assertWithMessage(eventsToString(eventCollector)).that(eventCollector.count()).isEqualTo(expectedCount);
    }

    /**
     * If the specified EventCollector contains an unexpected number of events, an informative
     * assertion fails in the context of the specified TestCase.
     */
    public static void assertEventCountAtLeast(int minCount, EventCollector eventCollector) {
        assertWithMessage(eventsToString(eventCollector)).that(eventCollector.count()).isAtLeast(minCount);
    }

    /**
     * If the specified EventCollector does not contain an event which has 'expectedEvent' as a
     * substring, an informative assertion fails. Otherwise the matching event is returned.
     */
    public static Event assertContainsEvent(Iterable<Event> eventCollector, String expectedEvent) {
        return assertContainsEvent(eventCollector, expectedEvent, EventKind.ALL_EVENTS);
    }

    /**
     * If the specified EventCollector does not contain an event which has
     * 'expectedEvent' as a substring, an informative assertion fails. Otherwise
     * the matching event is returned.
     */
    public static Event assertContainsEvent(Iterable<Event> eventCollector, String expectedEvent, EventKind kind) {
        return assertContainsEvent(eventCollector, expectedEvent, ImmutableSet.of(kind));
    }

    /**
     * If the specified EventCollector does not contain an event of a kind of 'kinds' which has
     * 'expectedEvent' as a substring, an informative assertion fails. Otherwise
     * the matching event is returned.
     */
    public static Event assertContainsEvent(Iterable<Event> eventCollector, String expectedEvent,
            Set<EventKind> kinds) {
        for (Event event : eventCollector) {
            // We want to be able to check for the location and the message type (error / warning).
            // Consequently, we use toString() instead of getMessage().
            if (event.toString().contains(expectedEvent) && kinds.contains(event.getKind())) {
                return event;
            }
        }
        String eventsString = eventsToString(eventCollector);
        assertWithMessage("Event '" + expectedEvent + "' not found"
                + (eventsString.length() == 0 ? "" : ("; found these though:" + eventsString))).that(false)
                        .isTrue();
        return null; // unreachable
    }

    /**
     * If the specified EventCollector contains an event which has
     * 'expectedEvent' as a substring, an informative assertion fails.
     */
    public static void assertDoesNotContainEvent(Iterable<Event> eventCollector, String expectedEvent) {
        for (Event event : eventCollector) {
            assertWithMessage(
                    "Unexpected string '" + expectedEvent + "' matched following event:\n" + event.getMessage())
                            .that(event.getMessage()).doesNotContain(expectedEvent);
        }
    }

    /**
     * If the specified EventCollector does not contain an event which has
     * each of {@code words} surrounded by single quotes as a substring, an
     * informative assertion fails.  Otherwise the matching event is returned.
     */
    public static Event assertContainsEventWithWordsInQuotes(Iterable<Event> eventCollector, String... words) {
        for (Event event : eventCollector) {
            boolean found = true;
            for (String word : words) {
                if (!event.getMessage().contains("'" + word + "'")) {
                    found = false;
                    break;
                }
            }
            if (found) {
                return event;
            }
        }
        String eventsString = eventsToString(eventCollector);
        assertWithMessage("Event containing words " + Arrays.toString(words) + " in " + "single quotes not found"
                + (eventsString.length() == 0 ? "" : ("; found these though:" + eventsString))).that(false)
                        .isTrue();
        return null; // unreachable
    }

    /**
     * Returns a string consisting of each event in the specified collector,
     * preceded by a newline.
     */
    private static String eventsToString(Iterable<Event> eventCollector) {
        StringBuilder buf = new StringBuilder();
        eventLoop: for (Event event : eventCollector) {
            for (String ignoredPrefix : TestConstants.IGNORED_MESSAGE_PREFIXES) {
                if (event.getMessage().startsWith(ignoredPrefix)) {
                    continue eventLoop;
                }
            }
            buf.append('\n').append(event);
        }
        return buf.toString();
    }

    /**
     * If "expectedSublist" is not a sublist of "arguments", an informative
     * assertion is failed in the context of the specified TestCase.
     *
     * <p>Argument order mnemonic: assert(X)ContainsSublist(Y).
     */
    @SuppressWarnings({ "unchecked", "varargs" })
    public static <T> void assertContainsSublist(List<T> arguments, T... expectedSublist) {
        List<T> sublist = Arrays.asList(expectedSublist);
        try {
            assertThat(Collections.indexOfSubList(arguments, sublist)).isNotEqualTo(-1);
        } catch (AssertionError e) {
            throw new AssertionError("Did not find " + sublist + " as a sublist of " + arguments, e);
        }
    }

    /**
     * If "expectedSublist" is a sublist of "arguments", an informative
     * assertion is failed in the context of the specified TestCase.
     *
     * <p>Argument order mnemonic: assert(X)DoesNotContainSublist(Y).
     */
    @SuppressWarnings({ "unchecked", "varargs" })
    public static <T> void assertDoesNotContainSublist(List<T> arguments, T... expectedSublist) {
        List<T> sublist = Arrays.asList(expectedSublist);
        try {
            assertThat(Collections.indexOfSubList(arguments, sublist)).isEqualTo(-1);
        } catch (AssertionError e) {
            throw new AssertionError("Found " + sublist + " as a sublist of " + arguments, e);
        }
    }

    /**
     * Check to see if each element of expectedMessages is the beginning of a message
     * in eventCollector, in order, as in {@link #containsSublistWithGapsAndEqualityChecker}.
     * If not, an informative assertion is failed
     */
    protected static void assertContainsEventsInOrder(Iterable<Event> eventCollector, String... expectedMessages) {
        String failure = containsSublistWithGapsAndEqualityChecker(ImmutableList.copyOf(eventCollector),
                new Function<Pair<Event, String>, Boolean>() {
                    @Override
                    public Boolean apply(Pair<Event, String> pair) {
                        return pair.first.getMessage().contains(pair.second);
                    }
                }, expectedMessages);

        String eventsString = eventsToString(eventCollector);
        assertWithMessage("Event '" + failure + "' not found in proper order"
                + (eventsString.length() == 0 ? "" : ("; found these though:" + eventsString))).that(failure)
                        .isNull();
    }

    /**
     * Check to see if each element of expectedSublist is in arguments, according to
     * the equalityChecker, in the same order as in expectedSublist (although with
     * other interspersed elements in arguments allowed).
     * @param equalityChecker function that takes a Pair<S, T> element and returns true
     * if the elements of the pair are equal by its lights.
     * @return first element not in arguments in order, or null if success.
     */
    @SuppressWarnings({ "unchecked" })
    protected static <S, T> T containsSublistWithGapsAndEqualityChecker(List<S> arguments,
            Function<Pair<S, T>, Boolean> equalityChecker, T... expectedSublist) {
        Iterator<S> iter = arguments.iterator();
        outerLoop: for (T expected : expectedSublist) {
            while (iter.hasNext()) {
                S actual = iter.next();
                if (equalityChecker.apply(Pair.of(actual, expected))) {
                    continue outerLoop;
                }
            }
            return expected;
        }
        return null;
    }

    public static List<Event> assertContainsEventWithFrequency(Iterable<Event> events, String expectedMessage,
            int expectedFrequency) {
        ImmutableList.Builder<Event> builder = ImmutableList.builder();
        for (Event event : events) {
            if (event.getMessage().contains(expectedMessage)) {
                builder.add(event);
            }
        }
        List<Event> foundEvents = builder.build();
        assertWithMessage(events.toString()).that(foundEvents).hasSize(expectedFrequency);
        return foundEvents;
    }
}