TestInformationLogger.java

package fr.sii.ogham.testing.extension.common;

import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.BOTTOM_LEFT;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.BOTTOM_RIGHT;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.HORIZONTAL;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.TOP_LEFT;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.TOP_RIGHT;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.VERTICAL;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.VERTICAL_LEFT;
import static fr.sii.ogham.testing.extension.common.TestInformationLogger.Characters.VERTICAL_RIGHT;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;

import org.apache.commons.lang3.StringUtils;

import com.github.jknack.handlebars.internal.text.WordUtils;

/**
 * Write information about test. This is useful when there are many tests:
 * <ul>
 * <li>To quickly find the logs for the test</li>
 * <li>To quickly know if the test has failed or succeeded</li>
 * <li>To quickly identify the test failure</li>
 * <li>To quickly find failed tests</li>
 * </ul>
 * 
 * @author Aurélien Baudet
 *
 */
@SuppressWarnings("squid:S1312")
public class TestInformationLogger {
	public static final int DEFAULT_MAX_LENGTH = 100;
	public static final String DEFAULT_MARKER = "test-info";
	private static final String[] SINGLE_UTF8 = { "┌", "┐", "└", "┘", "─", "│", "├", "┤" };
	private static final String[] DOUBLE_UTF8 = { "╔", "╗", "╚", "╝", "═", "║", "╠", "╣" };
	private static final String[] SINGLE_ASCII = { "+", "+", "+", "+", "-", "|", "+", "+" };
	private static final String[] DOUBLE_ASCII = { "+", "+", "+", "+", "=", "|", "+", "+" };

	private final int maxLength;
	private final Printer printer;
	private final String marker;
	private final String[] singleChars;
	private final String[] doubleChars;

	/**
	 * Initializes with the default max line length (100), uses this logger as
	 * printer and mark logs with "test-info" marker
	 */
	public TestInformationLogger() {
		this(DEFAULT_MAX_LENGTH);
	}

	/**
	 * Initializes with the provided max line length.
	 * 
	 * Uses this logger as printer and default marker ("test-info").
	 * 
	 * @param maxLength
	 *            the length of each line
	 */
	public TestInformationLogger(int maxLength) {
		this(maxLength, DEFAULT_MARKER);
	}

	/**
	 * Initializes with the provided max line length and marker.
	 * 
	 * Uses this logger as printer.
	 * 
	 * @param maxLength
	 *            the length of each line
	 * @param marker
	 *            the marker for logs
	 */
	public TestInformationLogger(int maxLength, String marker) {
		this(maxLength, marker, new Slf4jPrinter());
	}

	/**
	 * 
	 * @param maxLength
	 *            the length of each line
	 * @param marker
	 *            the marker for logs
	 * @param printer
	 *            the printer
	 */
	public TestInformationLogger(int maxLength, String marker, Printer printer) {
		super();
		this.maxLength = maxLength;
		this.printer = printer;
		this.marker = marker;
		this.singleChars = Charset.defaultCharset().contains(StandardCharsets.UTF_8) ? SINGLE_UTF8 : SINGLE_ASCII;
		this.doubleChars = Charset.defaultCharset().contains(StandardCharsets.UTF_8) ? DOUBLE_UTF8 : DOUBLE_ASCII;
	}

	/**
	 * Write the name of the test. The name is boxed to quickly see the test
	 * name in long logs.
	 * 
	 * @param testName
	 *            the name of the test
	 */
	public void writeStart(String testName) {
		// @formatter:off
		printer.printHeader(marker, 
				borderTop(doubleChars)+"\n"+ 
				format(testName, doubleChars)+"\n"+ 
				borderBottom(doubleChars));
		// @formatter:on
	}

	/**
	 * Write the name of the test and "SUCCESS" message. The name is boxed to
	 * quickly see the test name in long logs.
	 * 
	 * @param testName
	 *            the name of the test
	 */
	public void writeSuccess(String testName) {
		// @formatter:off
		printer.printSucess(marker,
					borderTop(singleChars)+"\n"+ 
					format(testName, singleChars)+"\n"+ 
					borderMiddle(singleChars)+"\n"+
					format("SUCCESS", singleChars)+"\n"+ 
					borderBottom(singleChars));
		// @formatter:on
	}

	/**
	 * Write the name of the test, "FAILED" message and failure information. The
	 * name is boxed to quickly see the test name in long logs.
	 * 
	 * @param testName
	 *            the name of the test
	 * @param e
	 *            the thrown exception
	 */
	@SuppressWarnings("squid:S4142")
	public void writeFailure(String testName, Throwable e) {
		// @formatter:off
		printer.printFailure(marker,
				borderTop(singleChars)+"\n"+
				format(testName, singleChars)+"\n"+ 
				borderMiddle(singleChars)+"\n"+
				format("FAILED", singleChars)+"\n"+
				borderMiddle(singleChars)+"\n"+
				format(e.toString(), singleChars)+"\n"+ 
				borderBottom(singleChars), e);
		// @formatter:on
	}

	private String borderTop(String[] characters) {
		return TOP_LEFT.character(characters) + dashLine(HORIZONTAL.character(characters)) + TOP_RIGHT.character(characters);
	}

	private String borderMiddle(String[] characters) {
		return VERTICAL_LEFT.character(characters) + dashLine(HORIZONTAL.character(characters)) + VERTICAL_RIGHT.character(characters);
	}

	private String borderBottom(String[] characters) {
		return BOTTOM_LEFT.character(characters) + dashLine(HORIZONTAL.character(characters)) + BOTTOM_RIGHT.character(characters);
	}

	private String dashLine(String character) {
		return StringUtils.repeat(character, maxLength - 2);
	}

	private String format(String text, String[] characters) {
		String vertical = VERTICAL.character(characters);
		StringJoiner joiner = new StringJoiner(vertical + "\n" + vertical, vertical, vertical);
		for (String line : wrap(text)) {
			joiner.add(StringUtils.rightPad(line, maxLength - 2));
		}
		return joiner.toString();
	}

	private List<String> wrap(String text) {
		return Arrays.asList(WordUtils.wrap(text.replace("\t", "  "), maxLength - 3, "\n", true).split("\n"));
	}

	enum Characters {
		TOP_LEFT(0),
		TOP_RIGHT(1),
		BOTTOM_LEFT(2),
		BOTTOM_RIGHT(3),
		HORIZONTAL(4),
		VERTICAL(5),
		VERTICAL_LEFT(6),
		VERTICAL_RIGHT(7);

		private final int pos;

		Characters(int pos) {
			this.pos = pos;
		}

		public String character(String[] characters) {
			return characters[pos];
		}
	}
}