TestLogger.java
package com.renomad.minum.logging;
import com.renomad.minum.state.Constants;
import com.renomad.minum.utils.MyThread;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.ReentrantLock;
/**
* This implementation of {@link Logger} has a few
* extra functions that only apply to tests, like {@link #test(String)}
*/
public final class TestLogger extends Logger {
private final Queue<String> recentLogLines;
public static final int MAX_CACHE_SIZE = 30;
private final ReentrantLock loggingLock;
private int testCount = 0;
/**
* See {@link TestLogger}
*/
public TestLogger(Constants constants, ExecutorService executorService, String name) {
super(constants, executorService, name);
this.recentLogLines = new TestLoggerQueue(MAX_CACHE_SIZE);
this.loggingLock = new ReentrantLock();
}
/**
* A helper to get the string message value out of a
* {@link ThrowingSupplier}
*/
private String extractMessage(ThrowingSupplier<String, Exception> msg) {
String receivedMessage;
try {
receivedMessage = msg.get();
} catch (Exception ex) {
receivedMessage = "EXCEPTION DURING GET: " + ex;
}
return receivedMessage;
}
/**
* Keeps a record of the recently-added log messages, which is
* useful for some tests.
*/
private void addToCache(ThrowingSupplier<String, Exception> msg) {
// put log messages into the tail of the queue
String message = extractMessage(msg);
String safeMessage = message == null ? "(null message)" : message;
recentLogLines.add(safeMessage);
}
@Override
public void logDebug(ThrowingSupplier<String, Exception> msg) {
loggingLock.lock();
try {
addToCache(msg);
super.logDebug(msg);
} finally {
loggingLock.unlock();
}
}
@Override
public void logTrace(ThrowingSupplier<String, Exception> msg) {
loggingLock.lock();
try {
addToCache(msg);
super.logTrace(msg);
} finally {
loggingLock.unlock();
}
}
@Override
public void logAudit(ThrowingSupplier<String, Exception> msg) {
loggingLock.lock();
try {
addToCache(msg);
super.logAudit(msg);
} finally {
loggingLock.unlock();
}
}
@Override
public void logAsyncError(ThrowingSupplier<String, Exception> msg) {
loggingLock.lock();
try {
addToCache(msg);
super.logAsyncError(msg);
} finally {
loggingLock.unlock();
}
}
/**
* Provides an ability to search over the recent past log messages,
* case-insensitively.
* @param lines number of lines of log messages to look back through,
* up to {@link #MAX_CACHE_SIZE}
*/
public String findFirstMessageThatContains(String value, int lines) {
List<String> values = findMessage(value, lines, recentLogLines);
List<String> logsBeingSearched = logLinesToSearch(lines, recentLogLines);
return checkValidityOfResults(value, values, logsBeingSearched);
}
/**
* This is used in {@link #findFirstMessageThatContains(String, int)} to
* handle exceptional situations with the results. Specifically, exceptions
* are:
* <ol>
* <li>If there were no results found</li>
* <li>If there were multiple results found</li>
* </ol>
*/
static String checkValidityOfResults(String value, List<String> values, List<String> recentLogLines) {
int size = values.size();
if (size == 0) {
throw new TestLoggerException(value + " was not found in \n\t" + String.join("\n\t", recentLogLines));
} else if (size >= 2) {
throw new TestLoggerException("multiple values of "+value+" found in: " + recentLogLines);
} else {
return values.getFirst();
}
}
/**
* Whether the given string exists in the log messages. May
* exist multiple times.
* @param value a string to search in the log
* @param lines how many lines back to examine
* @return whether this string was found, even if there
* were multiple places it was found.
*/
public boolean doesMessageExist(String value, int lines) {
if (! findMessage(value, lines, recentLogLines).isEmpty()) {
return true;
} else {
List<String> logsBeingSearched = logLinesToSearch(lines, recentLogLines);
throw new TestLoggerException(value + " was not found in \n\t" + String.join("\n\t", logsBeingSearched));
}
}
/**
* Whether the given string exists in the log messages. May
* exist multiple times.
* @param value a string to search in the log
* @return whether or not this string was found, even if there
* were multiple places it was found.
*/
public boolean doesMessageExist(String value) {
return doesMessageExist(value, 3);
}
static List<String> findMessage(String value, int lines, Queue<String> recentLogLines) {
if (lines > MAX_CACHE_SIZE) {
throw new TestLoggerException(String.format("Can only get up to %s lines from the log", MAX_CACHE_SIZE));
}
if (lines <= 0) {
throw new TestLoggerException("number of recent log lines must be a positive number");
}
MyThread.sleep(20);
var lineList = logLinesToSearch(lines, recentLogLines);
return lineList.stream().filter(x -> x.toLowerCase(Locale.ROOT).contains(value.toLowerCase(Locale.ROOT))).toList();
}
private static List<String> logLinesToSearch(int lines, Queue<String> recentLogLines) {
var fromIndex = Math.max(recentLogLines.size() - lines, 0);
return recentLogLines.stream().toList().subList(fromIndex, recentLogLines.size());
}
/**
* Looks back through the last 3 log messages for one that
* contains the provided value. Returns the whole line if
* found and an exception if not found.
* <p>
* See {@link #findFirstMessageThatContains(String, int)} if you
* want to search through more than 3. However, it is only
* possible to search up to {@link #MAX_CACHE_SIZE}
* </p>
*/
public String findFirstMessageThatContains(String value) {
return findFirstMessageThatContains(value, 3);
}
/**
* A helper function to log a test title prefixed with "TEST:"
* <br>
* Also collects data about the previously-run test
*/
public void test(String msg) {
// put together some pretty-looking text graphics to show the suiteName of our test in log
loggingLock.lock();
try {
final var baseLength = 11;
final var dashes = "-".repeat(msg.length() + baseLength);
loggingActionQueue.enqueue("Testlogger#test("+msg+")", () -> {
testCount += 1;
System.out.printf("%n+%s+%n| TEST %d: %s |%n+%s+%n%n", dashes, testCount, msg, dashes);
recentLogLines.add(msg);
});
} finally {
loggingLock.unlock();
}
}
public int getTestCount() {
return testCount;
}
@Override
public String toString() {
return "TestLogger using queue: " + super.loggingActionQueue.toString();
}
}