package com.equalexperts.logging; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.LongAdder; import java.util.function.Function; import java.util.stream.Collectors; import static com.equalexperts.logging.EnumContractRunner.EnumField; import static java.util.Arrays.stream; import static java.util.function.Function.identity; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; @RunWith(EnumContractRunner.class) public abstract class LogMessageContractTest<T extends Enum<T> & LogMessage> { @SuppressWarnings("UnusedDeclaration") @EnumField private T enumValue; @Test public void getMessageCode_shouldReturnAUniqueValue() throws Exception { if (enumValue.getMessageCode() == null) { return; //don't test duplication for null values — too complicated anyway } if (enumValue.getMessageCode().equals("")) { return; //don't test duplication for empty string values — too complicated anyway } List<String> otherLogMessagesWithThisCode = stream(enumValue.getDeclaringClass().getEnumConstants()) .filter(t -> t != enumValue) .filter(t -> enumValue.getMessageCode().equalsIgnoreCase(t.getMessageCode())) .map(this::formatForErrorMessage) .collect(Collectors.toList()); if (!otherLogMessagesWithThisCode.isEmpty()) { fail(enumValue.name() + " has the same code as " + String.join(",", otherLogMessagesWithThisCode)); } } @Test public void getMessageCode_shouldNotBeNull() throws Exception { assertNotNull(enumValue.getMessageCode()); } @Test public void getMessageCode_shouldNotBeEmptyString() throws Exception { if("".equals(enumValue.getMessageCode())) { fail("A proper message code is required"); } } @Test public void getMessagePattern_shouldNotBeNull() throws Exception { assertNotNull(enumValue.getMessagePattern()); } @Test public void getMessagePattern_shouldNotBeAnEmptyString() throws Exception { if("".equals(enumValue.getMessagePattern())) { fail("A proper message pattern is required"); } } @Test public void enumInstance_shouldBeImmutable() throws Exception { assertInstancesOf(enumValue.getClass(), areImmutable()); } @Test public void messageCodes_shouldAllBeTheSameLength() throws Exception { int mostCommonLength = getMostCommonLength(enumValue.getDeclaringClass().getEnumConstants()); assertEquals(enumValue.name() + " has a different length than " + mostCommonLength + ", the most common length", mostCommonLength, enumValue.getMessageCode().length()); } private int getMostCommonLength(T[] constants) { //calculate a histogram of messageCodeLengths Map<Integer, Long> codeLengthHistogram = stream(constants) .map(c -> c.getMessageCode().length()) .collect(Collectors.groupingBy(identity(), Collectors.counting())); //most common count return codeLengthHistogram.entrySet().stream() .max(Comparator.comparingInt(e -> e.getValue().intValue())) .map(Map.Entry::getKey) .get(); } private String formatForErrorMessage(T value) { return value.getClass().getSimpleName() + "." + value.name(); } }