package com.equalexperts.logging.impl;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static java.nio.file.StandardOpenOption.*;
public class FilesystemStackTraceProcessorTest {
private final Path mockDestinationDirectory = mock(Path.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS));
private final ThrowableFingerprintCalculator fingerprintCalculator = mock(ThrowableFingerprintCalculator.class);
private final StackTraceProcessor processor = new FilesystemStackTraceProcessor(mockDestinationDirectory, fingerprintCalculator);
@Test
public void process_shouldStoreTheStackTraceInAFileBasedOnTheFingerPrint() throws Exception {
//setup is hairy — mocking a lot of file IO
Throwable expectedException = new RuntimeException("blah!");
String expectedFingerprint = "12345";
String expectedFilename = "stacktrace_" + expectedFingerprint + ".txt";
String expectedStacktraceUri = "file:///tmp/log/" + expectedFilename;
String expectedMessage = expectedException.toString() + " (" + expectedStacktraceUri + ")";
when(fingerprintCalculator.calculateFingerprint(expectedException)).thenReturn(expectedFingerprint);
Path expectedPath = mock(Path.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS));
when(mockDestinationDirectory.resolve(expectedFilename)).thenReturn(expectedPath);
when(expectedPath.toUri()).thenReturn(new URI(expectedStacktraceUri));
pretendMockPathDoesNotExist(expectedPath);
ByteArrayOutputStream simulatedFileOutputStream = new ByteArrayOutputStream();
when(Files.newOutputStream(expectedPath, CREATE_NEW, WRITE)).thenReturn(simulatedFileOutputStream);
TestPrintStream expectedFileContents = new TestPrintStream();
expectedException.printStackTrace(expectedFileContents);
StringBuilder output = new StringBuilder();
//execute
processor.process(expectedException, output);
//assert
verify(expectedPath.getFileSystem().provider()).newOutputStream(expectedPath, CREATE_NEW, WRITE);
assertEquals(expectedFileContents.toString(), new String(simulatedFileOutputStream.toByteArray()));
assertEquals(expectedMessage, output.toString());
}
@Test
public void process_shouldReuseTheURIOfTheExistingFile_whenItAlreadyExists() throws Exception {
//setup is hairy — mocking a lot of file IO
Throwable expectedException = new RuntimeException("blah!");
String expectedFingerprint = "12345";
String expectedFilename = "stacktrace_" + expectedFingerprint + ".txt";
String expectedStacktraceUri = "file:///tmp/log/" + expectedFilename;
String expectedMessage = expectedException.toString() + " (" + expectedStacktraceUri + ")";
when(fingerprintCalculator.calculateFingerprint(expectedException)).thenReturn(expectedFingerprint);
Path expectedPath = mock(Path.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS));
when(mockDestinationDirectory.resolve(expectedFilename)).thenReturn(expectedPath);
when(expectedPath.toUri()).thenReturn(new URI(expectedStacktraceUri));
StringBuilder output = new StringBuilder();
//execute
processor.process(expectedException, output);
//assert
assertEquals(expectedMessage, output.toString());
verify(expectedPath.getFileSystem().provider()).checkAccess(expectedPath);
verifyNoMoreInteractions(expectedPath.getFileSystem().provider());
}
@Test
public void process_shouldReuseTheURIOfTheExistingFile_whenItAlreadyExistsInARaceCondition() throws Exception {
//setup is hairy — mocking a lot of file IO
Throwable expectedException = new RuntimeException("blah!");
String expectedFingerprint = "12345";
String expectedFilename = "stacktrace_" + expectedFingerprint + ".txt";
String expectedStacktraceUri = "file:///tmp/log/" + expectedFilename;
String expectedMessage = expectedException.toString() + " (" + expectedStacktraceUri + ")";
when(fingerprintCalculator.calculateFingerprint(expectedException)).thenReturn(expectedFingerprint);
Path expectedPath = mock(Path.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS));
when(mockDestinationDirectory.resolve(expectedFilename)).thenReturn(expectedPath);
when(expectedPath.toUri()).thenReturn(new URI(expectedStacktraceUri));
pretendMockPathDoesNotExist(expectedPath);
//filesystem reports that the file doesn't exist, but fail when creating (simulate a race condition)
when(Files.newOutputStream(expectedPath, CREATE_NEW, WRITE)).thenThrow(new FileAlreadyExistsException("blah"));
TestPrintStream expectedFileContents = new TestPrintStream();
expectedException.printStackTrace(expectedFileContents);
StringBuilder output = new StringBuilder();
//execute
processor.process(expectedException, output);
//assert
verify(expectedPath.getFileSystem().provider()).checkAccess(expectedPath);
verify(expectedPath.getFileSystem().provider()).newOutputStream(expectedPath, CREATE_NEW, WRITE);
verifyNoMoreInteractions(expectedPath.getFileSystem().provider());
assertEquals(expectedMessage, output.toString());
}
private void pretendMockPathDoesNotExist(Path expectedPath) throws Exception {
FileSystemProvider mockProvider = expectedPath.getFileSystem().provider();
doThrow(NoSuchFileException.class).when(mockProvider).checkAccess(expectedPath); //notExists check
//for exists, simulate a path not existing by failing to read attributes
when(Files.readAttributes(expectedPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(new IOException("File does not exist"));
}
}