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

net.openhft.chronicle.testframework.internal.VanillaExceptionTracker Maven / Gradle / Ivy

package net.openhft.chronicle.testframework.internal;

import net.openhft.chronicle.testframework.exception.ExceptionTracker;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * A test utility class for recording and executing assertions about the presence (or absence) of exceptions
 *
 * @param  The class used to represent thrown exceptions
 */
@SuppressWarnings("deprecation")
public final class VanillaExceptionTracker implements ExceptionTracker, net.openhft.chronicle.testframework.internal.ExceptionTracker {

    private static final Logger LOGGER = LoggerFactory.getLogger(VanillaExceptionTracker.class);

    private final Map, String> ignoredExceptions = new LinkedHashMap<>();
    private final Map, String> expectedExceptions = new LinkedHashMap<>();
    private final Function messageExtractor;
    private final Function throwableExtractor;
    private final Runnable resetRunnable;
    private final Map exceptions;
    private final Predicate ignorePredicate;
    private final Function exceptionRenderer;
    private boolean finalised = false;

    public VanillaExceptionTracker(@NotNull final Function messageExtractor,
                                   @NotNull final Function throwableExtractor,
                                   @NotNull final Runnable resetRunnable,
                                   @NotNull final Map exceptions,
                                   @NotNull final Predicate ignorePredicate) {
        this(messageExtractor, throwableExtractor, resetRunnable, exceptions, ignorePredicate, String::valueOf);
    }

    public VanillaExceptionTracker(@NotNull final Function messageExtractor,
                                   @NotNull final Function throwableExtractor,
                                   @NotNull final Runnable resetRunnable,
                                   @NotNull final Map exceptions,
                                   @NotNull final Predicate ignorePredicate,
                                   @NotNull final Function exceptionRenderer) {
        this.messageExtractor = messageExtractor;
        this.throwableExtractor = throwableExtractor;
        this.resetRunnable = resetRunnable;
        this.exceptions = exceptions;
        this.ignorePredicate = ignorePredicate;
        this.exceptionRenderer = exceptionRenderer;
    }

    private static boolean contains(String text, String message) {
        return text != null && text.contains(message);
    }

    /**
     * Does this Throwable or any of its causes contain the specified text?
     *
     * @param text The substring to search for
     * @return true if there are any matches for it, false otherwise
     */
    private static boolean throwableContainsTextRecursive(@NotNull String text, Throwable throwable) {
        return throwableContainsTextRecursive(text, throwable, new HashSet<>());
    }

    private static boolean throwableContainsTextRecursive(@NotNull String text, Throwable throwable, Set seenThrowableIDs) {
        if (throwable == null || seenThrowableIDs.contains(System.identityHashCode(throwable))) {
            return false;
        }
        if (throwable.getMessage() != null && throwable.getMessage().contains(text)) {
            return true;
        }
        seenThrowableIDs.add(System.identityHashCode(throwable));
        return throwableContainsTextRecursive(text, throwable.getCause(), seenThrowableIDs);
    }

    @Override
    public void expectException(@NotNull String message) {
        expectException(k -> contains(messageExtractor.apply(k), message) || throwableContainsTextRecursive(message, throwableExtractor.apply(k)), message);
    }

    @Override
    public void expectException(Predicate predicate, String description) {
        checkFinalised();
        expectedExceptions.put(predicate, description);
    }

    @Override
    public void ignoreException(@NotNull String message) {
        ignoreException(k -> contains(messageExtractor.apply(k), message) || throwableContainsTextRecursive(message, throwableExtractor.apply(k)), message);
    }

    @Override
    public void ignoreException(Predicate predicate, String description) {
        checkFinalised();
        ignoredExceptions.put(predicate, description);
    }

    @Override
    public boolean hasException(Predicate predicate) {
        return exceptions.keySet().stream().anyMatch(predicate);
    }

    @Override
    public void checkExceptions() {
        checkFinalised();
        finalised = true;
        for (Map.Entry, String> expectedException : expectedExceptions.entrySet()) {
            if (!exceptions.keySet().removeIf(expectedException.getKey()))
                throw new AssertionError("No error for " + expectedException.getValue());
        }
        for (Map.Entry, String> ignoredException : ignoredExceptions.entrySet()) {
            if (exceptions.keySet().removeIf(ignoredException.getKey()))
                LOGGER.debug("Ignored {}", ignoredException.getValue());
        }

        if (hasExceptions()) {
            dumpException();

            final String msg = exceptions.size() + " exceptions were detected: " + exceptions.keySet().stream().map(messageExtractor::apply).collect(Collectors.joining(", "));
            throw new AssertionError(msg);
        }
        resetRunnable.run();
    }

    private boolean hasExceptions() {
        for (T k : exceptions.keySet()) {
            if (!ignorePredicate.test(k))
                return true;
        }

        return false;
    }

    private void dumpException() {
        for (@NotNull Map.Entry entry : exceptions.entrySet()) {
            final T key = entry.getKey();
            LOGGER.warn(exceptionRenderer.apply(key), throwableExtractor.apply(key));
            final Integer value = entry.getValue();
            if (value > 1)
                LOGGER.warn("Repeated {} times", value);
        }
    }

    private void checkFinalised() {
        if (finalised) {
            throw new IllegalStateException("VanillaExceptionTracker is single use, you create it, add expectations/ignores, run tests, call check and then dispose of it.");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy