package com.equalexperts.logging.impl; import com.equalexperts.logging.DiagnosticContextSupplier; import com.equalexperts.logging.LogMessage; import com.equalexperts.logging.RestoreSystemStreamsFixture; import com.equalexperts.logging.TempFileFixture; import org.junit.Rule; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Consumer; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.*; import static org.mockito.Matchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public class InfrastructureFactoryTest { @Rule public final RestoreSystemStreamsFixture systemStreamsFixture = new RestoreSystemStreamsFixture(); @Rule public final TempFileFixture tempFiles = new TempFileFixture(); private static final Optional<Path> SAMPLE_LOGFILE_PATH = Optional.empty(); private static final Optional<PrintStream> SAMPLE_LOGGER_OUTPUT = Optional.of(System.out); private static final Optional<Boolean> SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM = Optional.of(false); private static final Optional<Path> SAMPLE_STACK_TRACE_STORAGE_PATH = Optional.empty(); private static final Optional<DiagnosticContextSupplier> SAMPLE_CONTEXT_SUPPLIER = Optional.empty(); private static final Optional<Consumer<Throwable>> SAMPLE_ERROR_HANDLER = Optional.of(e -> {}); @Test public void emptyCorrelationIdSupplier_shouldAlwaysProduceAnEmptyMap() throws Exception { Map<String, String> defaultCorrelationIds = InfrastructureFactory.EMPTY_CONTEXT_SUPPLIER.getMessageContext(); assertNotNull(defaultCorrelationIds); assertEquals(0, defaultCorrelationIds.size()); } @Test public void defaultErrorHandler_shouldPrintAStackTraceToSystemError() throws Exception { Throwable mockThrowable = mock(Throwable.class); InfrastructureFactory.DEFAULT_ERROR_HANDLER.accept(mockThrowable); verify(mockThrowable).printStackTrace(same(System.err)); } @Test public void configureCorrelationIdSupplier_shouldReturnTheProvidedSupplier_whenOneIsProvided() throws Exception { DiagnosticContextSupplier expectedSupplier = HashMap::new; //don't use Collections.emptyMap, because that's the default InfrastructureFactory factory = new InfrastructureFactory( SAMPLE_LOGFILE_PATH, SAMPLE_LOGGER_OUTPUT, SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM, SAMPLE_STACK_TRACE_STORAGE_PATH, Optional.of(expectedSupplier), SAMPLE_ERROR_HANDLER); DiagnosticContextSupplier actualSupplier = factory.configureContextSupplier(); assertSame(expectedSupplier, actualSupplier); } @Test public void configureCorrelationIdSupplier_shouldReturnTheDefaultSupplier_whenOneIsNotProvided() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( SAMPLE_LOGFILE_PATH, SAMPLE_LOGGER_OUTPUT, SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM, SAMPLE_STACK_TRACE_STORAGE_PATH, Optional.empty(), SAMPLE_ERROR_HANDLER); DiagnosticContextSupplier actualSupplier = factory.configureContextSupplier(); assertSame(InfrastructureFactory.EMPTY_CONTEXT_SUPPLIER, actualSupplier); } @Test public void configureErrorHandler_shouldReturnTheProvidedErrorHAndler_whenOneIsProvided() throws Exception { Consumer<Throwable> expectedErrorHandler = t -> {}; InfrastructureFactory factory = new InfrastructureFactory( SAMPLE_LOGFILE_PATH, SAMPLE_LOGGER_OUTPUT, SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM, SAMPLE_STACK_TRACE_STORAGE_PATH, SAMPLE_CONTEXT_SUPPLIER, Optional.of(expectedErrorHandler)); Consumer<Throwable> actualErrorHandler = factory.configureErrorHandler(); assertSame(expectedErrorHandler, actualErrorHandler); } @Test public void configureErrorHandler_shouldReturnTheDefaultErrorHAndle_whenOneIsNotProvided() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( SAMPLE_LOGFILE_PATH, SAMPLE_LOGGER_OUTPUT, SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM, SAMPLE_STACK_TRACE_STORAGE_PATH, SAMPLE_CONTEXT_SUPPLIER, Optional.empty()); Consumer<Throwable> actualErrorHandler = factory.configureErrorHandler(); assertSame(InfrastructureFactory.DEFAULT_ERROR_HANDLER, actualErrorHandler); } @Test public void configureDestination_shouldCreateASimpleStackTraceProcessor_whenLoggingToAPathAndStoringStackTracesInTheFileSystemIsExplicitlyDisabled() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( Optional.of(tempFiles.createTempFile(".log")), Optional.empty(), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(SimpleStackTraceProcessor.class)); } @Test public void configureDestination_shouldCreateASimpleStackTraceProcessor_whenLoggingToAStreamAndStoringStackTracesInTheFileSystemIsExplicitlyDisabled() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(System.err), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(SimpleStackTraceProcessor.class)); } @Test public void configureDestination_shouldStoreStackTracesInTheFileSystem_whenLoggingToAPathAndAStacktraceStoragePathHasBeenExplicitlyProvided() throws Exception { Path expectedStackTraceStoragePath = tempFiles.createTempDirectory(); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(tempFiles.createTempFile(".log")), Optional.empty(), Optional.of(true), //will always be true when a path is provided Optional.of(expectedStackTraceStoragePath), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(FilesystemStackTraceProcessor.class)); FilesystemStackTraceProcessor fs = (FilesystemStackTraceProcessor) stackTraceProcessor; assertSame(expectedStackTraceStoragePath, fs.getDestination()); } @Test public void configureDestination_shouldStoreStackTracesInTheFileSystem_whenLoggingToAStreamAndAStacktraceStoragePathHasBeenExplicitlyProvided() throws Exception { Path expectedStackTraceStoragePath = tempFiles.createTempDirectory(); InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(System.err), Optional.of(true), //will always be true when a path is provided Optional.of(expectedStackTraceStoragePath), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(FilesystemStackTraceProcessor.class)); FilesystemStackTraceProcessor fs = (FilesystemStackTraceProcessor) stackTraceProcessor; assertSame(expectedStackTraceStoragePath, fs.getDestination()); } @Test public void configureDestination_shouldStoreStackTracesInTheSameDirectoryAsTheLogFile_whenLoggingToAPathAndStoringStackTracesHasNotBeenExplicitlyConfigured() throws Exception { Path logFile = tempFiles.createTempFile(".log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), Optional.empty(), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(FilesystemStackTraceProcessor.class)); FilesystemStackTraceProcessor fs = (FilesystemStackTraceProcessor) stackTraceProcessor; assertEquals(logFile.getParent(), fs.getDestination()); } @Test public void configureDestination_shouldStoreStackTracesInTheSameDirectoryAsTheLogFile_whenLoggingToAPathAndStoringStackTracesHasBeenExplicitlyEnabledButNoLocationHasBeenProvided() throws Exception { Path logFile = tempFiles.createTempFile(".log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), Optional.of(true), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(FilesystemStackTraceProcessor.class)); FilesystemStackTraceProcessor fs = (FilesystemStackTraceProcessor) stackTraceProcessor; assertEquals(logFile.getParent(), fs.getDestination()); } @Test public void configureDestination_shouldNotStoreStackTracesInTheFileSystem_whenLoggingToAStreamAndStoringStackTracesHasNotExplicitlyBeenConfigured() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(System.err), Optional.empty(), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); StackTraceProcessor stackTraceProcessor = factory.<TestMessages>configureDestination().getStackTraceProcessor(); assertThat(stackTraceProcessor, instanceOf(SimpleStackTraceProcessor.class)); } @Test public void configureDestination_shouldThrowAnIllegalStateException_whenNoLogfileIsProvidedAndStoringStackTracesInTheFileSystemHasBeenEnabledAndNoDestinationHasBeenProvided() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(System.err), Optional.of(true), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); try { factory.configureDestination(); fail("expected an IllegalStateException"); } catch (IllegalStateException ignore) { } } @Test public void configureDestination_shouldCreateTheStackTraceStoragePath_whenItDoesNotExistAndIsNotASymlink() throws Exception { Path storagePathParent = tempFiles.createTempDirectoryThatDoesNotExist(); Path storagePath = storagePathParent.resolve(UUID.randomUUID().toString()); InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(System.err), Optional.of(true), Optional.of(storagePath), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); factory.configureDestination(); assertTrue("storagePathParent must exist", Files.isDirectory(storagePathParent)); assertTrue("storagePath must exist", Files.isDirectory(storagePath)); } @Test public void configureDestination_shouldNotCreateTheStackTraceStoragePath_whenItIsASymlink() throws Exception { Path actualDestination = tempFiles.createTempDirectory(); Path symLinkDestination = tempFiles.createTempDirectoryThatDoesNotExist(); Files.createSymbolicLink(symLinkDestination, actualDestination); InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(System.err), Optional.of(true), Optional.of(symLinkDestination), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); factory.configureDestination(); //a FileAlreadyExistsException will be thrown if the code doesn't do the right thing } @Test public void configureDestination_shouldLogToSystemOut_whenNoPathOrStreamIsProvided() throws Exception { InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.empty(), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); Destination<TestMessages> destination = factory.configureDestination(); assertThat(destination, instanceOf(OutputStreamDestination.class)); OutputStreamDestination osd = (OutputStreamDestination) destination; assertSame(System.out, osd.getOutput()); } @Test public void configureDestination_shouldLogToTheProvidedPrintStream_givenAPrintStreamIsProvided() throws Exception { PrintStream ps = new PrintStream(new ByteArrayOutputStream()); InfrastructureFactory factory = new InfrastructureFactory( Optional.empty(), Optional.of(ps), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); Destination<TestMessages> destination = factory.configureDestination(); assertThat(destination, instanceOf(OutputStreamDestination.class)); OutputStreamDestination osd = (OutputStreamDestination) destination; assertSame(ps, osd.getOutput()); } @Test public void configureDestination_shouldLogToTheProvidedPath_whenALogfilePathIsProvided() throws Exception { Path logFile = tempFiles.createTempFile(".log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); Destination<TestMessages> destination = factory.configureDestination(); assertThat(destination, instanceOf(PathDestination.class)); PathDestination psd = (PathDestination) destination; assertSame(logFile, psd.getProvider().getPath()); } @Test public void configureDestination_shouldCorrectlyConfigureAPathDestinationAndFileChannelProvider_whenLoggingToAPath() throws Exception { Path logFile = tempFiles.createTempFile(".log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM, SAMPLE_STACK_TRACE_STORAGE_PATH, SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); Destination<TestMessages> destination = factory.configureDestination(); assertThat(destination, instanceOf(PathDestination.class)); PathDestination<TestMessages> psd = (PathDestination<TestMessages>) destination; FileChannelProvider provider = psd.getProvider(); assertSame(logFile, provider.getPath()); } @Test public void configureDestination_shouldRegisterTheCreatedDestinationWithTheRegistry_whenLoggingToAPath() throws Exception { Path logFile = tempFiles.createTempFile(".log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), SAMPLE_STORE_STACK_TRACES_IN_FILESYSTEM, SAMPLE_STACK_TRACE_STORAGE_PATH, SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); PathDestination<TestMessages> psd = (PathDestination<TestMessages>) factory.<TestMessages>configureDestination(); assertSame(ActiveRotationRegistry.getSingletonInstance(), psd.getActiveRotationRegistry()); } @Test public void configureDestination_shouldCreateTheLogFileParentDirectories_whenLoggingToAPathThatIsNotASymlink() throws Exception { Path grandParent = tempFiles.createTempDirectoryThatDoesNotExist(); Path parent = grandParent.resolve(UUID.randomUUID().toString()); Path logFile = parent.resolve("foo.log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); factory.configureDestination(); assertTrue("grand parent directory must be created", Files.isDirectory(grandParent)); //ensures nested creation is used assertTrue("parent directory must be created", Files.isDirectory(parent)); } @Test public void configureDestination_shouldNotCreateParentDirectories_givenAParentDirectoryThatIsASymlink() throws Exception { Path exists = tempFiles.createTempDirectory(); Path symLinkParent = tempFiles.createTempDirectoryThatDoesNotExist(); Files.createSymbolicLink(symLinkParent, exists); Path logFile = symLinkParent.resolve("foo.log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); factory.configureDestination(); //a FileAlreadyExistsException will be thrown if the code doesn't do the right thing } @Test public void configureDestination_shouldThrowAnUncheckedIOException_givenAProblemCreatingDirectories() throws Exception { Path invalidParent = tempFiles.createTempFile(".log"); Path logFile = invalidParent.resolve("foo.log"); InfrastructureFactory factory = new InfrastructureFactory( Optional.of(logFile), Optional.empty(), Optional.of(false), Optional.empty(), SAMPLE_CONTEXT_SUPPLIER, SAMPLE_ERROR_HANDLER); try { factory.configureDestination(); fail("expected an UncheckedUIException"); } catch (UncheckedIOException e) { assertThat(e.getCause(), instanceOf(IOException.class)); } } private enum TestMessages implements LogMessage { ; //don't actually need any messages for these tests //region LogMessage implementation guts private final String messageCode; private final String messagePattern; TestMessages(String messageCode, String messagePattern) { this.messageCode = messageCode; this.messagePattern = messagePattern; } @Override public String getMessageCode() { return messageCode; } @Override public String getMessagePattern() { return messagePattern; } //endregion } }