/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.logstash.logback.stacktrace;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluator;
public class ShortenedThrowableConverterTest {
private static class StackTraceElementGenerator {
public static void generateSingle() {
oneSingle();
}
public static void oneSingle() {
twoSingle();
}
private static void twoSingle() {
threeSingle();
}
private static void threeSingle() {
four();
}
private static void four() {
five();
}
private static void five() {
six();
}
private static void six() {
seven();
}
private static void seven() {
eight();
}
private static void eight() {
throw new RuntimeException("message");
}
public static void generateCausedBy() {
oneCausedBy();
}
private static void oneCausedBy() {
twoCausedBy();
}
private static void twoCausedBy() {
try {
threeSingle();
} catch (RuntimeException e) {
throw new RuntimeException("wrapper", e);
}
}
public static void generateSuppressed() {
oneSuppressed();
}
private static void oneSuppressed() {
twoSuppressed();
}
private static void twoSuppressed() {
try {
threeSingle();
} catch (RuntimeException e) {
RuntimeException newException = new RuntimeException();
newException.addSuppressed(e);
throw newException;
}
}
}
@Test
public void testDepthTruncation() {
try {
StackTraceElementGenerator.generateSingle();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
/*
* First get the un-truncated length
*/
converter.setMaxDepthPerThrowable(ShortenedThrowableConverter.FULL_MAX_DEPTH_PER_THROWABLE);
String formatted = converter.convert(createEvent(e));
int totalLines = countLines(formatted);
/*
* Now truncate and compare
*/
converter.setMaxDepthPerThrowable(totalLines - 5);
formatted = converter.convert(createEvent(e));
Assert.assertEquals(totalLines - 3, countLines(formatted));
Assert.assertTrue(formatted.contains("4 frames truncated"));
}
}
@Test
public void testLengthTruncation() {
try {
StackTraceElementGenerator.generateSingle();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
/*
* First get the un-truncated length
*/
converter.setMaxDepthPerThrowable(ShortenedThrowableConverter.FULL_MAX_DEPTH_PER_THROWABLE);
String formatted = converter.convert(createEvent(e));
int totalLength = formatted.length();
/*
* Now truncate and compare
*/
converter.setMaxLength(totalLength - 10);
formatted = converter.convert(createEvent(e));
Assert.assertEquals(totalLength - 10, formatted.length());
Assert.assertTrue(formatted.endsWith("..."));
}
}
@Test
public void testExclusion_consecutive() {
try {
StackTraceElementGenerator.generateSingle();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.addExclude("one");
converter.addExclude("two");
converter.addExclude("four");
converter.addExclude("five");
converter.addExclude("six");
converter.setMaxDepthPerThrowable(8);
String formatted = converter.convert(createEvent(e));
Assert.assertTrue(formatted.contains("2 frames excluded"));
Assert.assertTrue(formatted.contains("3 frames excluded"));
Assert.assertEquals(12, countLines(formatted));
}
}
@Test
public void testExclusion_noConsecutive() {
try {
StackTraceElementGenerator.generateSingle();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setExcludes(Collections.singletonList("one"));
converter.setMaxDepthPerThrowable(8);
String formatted = converter.convert(createEvent(e));
Assert.assertFalse(formatted.contains("frames excluded"));
Assert.assertEquals(10, countLines(formatted));
}
}
@Test
public void testExclusion_atEnd() {
try {
StackTraceElementGenerator.generateSingle();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
/*
* First get the un-truncated stacktrace
*/
converter.setMaxDepthPerThrowable(ShortenedThrowableConverter.FULL_MAX_DEPTH_PER_THROWABLE);
String formatted = converter.convert(createEvent(e));
/*
* Find the last two frames
*/
List<String> lines = getLines(formatted);
/*
* Now truncate and compare
*/
converter.addExclude(extractClassAndMethod(lines.get(lines.size() - 2)) + "$");
converter.addExclude(extractClassAndMethod(lines.get(lines.size() - 1)) + "$");
formatted = converter.convert(createEvent(e));
Assert.assertEquals(lines.size() - 1, countLines(formatted));
Assert.assertTrue(formatted.contains("2 frames excluded"));
}
}
@Test
public void testCausedBy() {
try {
StackTraceElementGenerator.generateCausedBy();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setMaxDepthPerThrowable(8);
String formatted = converter.convert(createEvent(e));
Assert.assertTrue(formatted.contains("Caused by"));
Assert.assertTrue(formatted.contains("common frames omitted"));
Assert.assertTrue(formatted.indexOf("message") > formatted.indexOf("wrapper"));
}
}
@Test
public void testRootCauseFirst() {
try {
StackTraceElementGenerator.generateCausedBy();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setRootCauseFirst(true);
converter.setMaxDepthPerThrowable(8);
String formatted = converter.convert(createEvent(e));
Assert.assertTrue(formatted.contains("Wrapped by"));
Assert.assertTrue(formatted.contains("common frames omitted"));
Assert.assertTrue(formatted.indexOf("message") < formatted.indexOf("wrapper"));
}
}
@SuppressWarnings("rawtypes")
@Test
public void testEvaluator() throws EvaluationException {
try {
StackTraceElementGenerator.generateCausedBy();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
EventEvaluator evaluator = mock(EventEvaluator.class);
when(evaluator.evaluate(any(Object.class))).thenReturn(true);
converter.addEvaluator(evaluator);
String formatted = converter.convert(createEvent(e));
Assert.assertEquals("", formatted);
}
}
@SuppressWarnings("rawtypes")
@Test
public void testOptions() throws EvaluationException {
EventEvaluator evaluator = mock(EventEvaluator.class);
Map<String, EventEvaluator> evaluatorMap = new HashMap<String, EventEvaluator>();
evaluatorMap.put("evaluator", evaluator);
Context context = mock(Context.class);
when(context.getObject(CoreConstants.EVALUATOR_MAP)).thenReturn(evaluatorMap);
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setContext(context);
// test full values
converter.setOptionList(Arrays.asList("full", "full", "full", "rootFirst", "evaluator", "regex"));
converter.start();
Assert.assertEquals(ShortenedThrowableConverter.FULL_MAX_DEPTH_PER_THROWABLE, converter.getMaxDepthPerThrowable());
Assert.assertEquals(ShortenedThrowableConverter.FULL_CLASS_NAME_LENGTH, converter.getShortenedClassNameLength());
Assert.assertEquals(ShortenedThrowableConverter.FULL_MAX_LENGTH, converter.getMaxLength());
Assert.assertEquals(true, converter.isRootCauseFirst());
Assert.assertEquals(evaluator, converter.getEvaluators().get(0));
Assert.assertEquals("regex", converter.getExcludes().get(0));
// test short values
converter.setOptionList(Arrays.asList("short", "short", "short", "rootFirst", "evaluator", "regex"));
converter.start();
Assert.assertEquals(ShortenedThrowableConverter.SHORT_MAX_DEPTH_PER_THROWABLE, converter.getMaxDepthPerThrowable());
Assert.assertEquals(ShortenedThrowableConverter.SHORT_CLASS_NAME_LENGTH, converter.getShortenedClassNameLength());
Assert.assertEquals(ShortenedThrowableConverter.SHORT_MAX_LENGTH, converter.getMaxLength());
// test numeric values
converter.setOptionList(Arrays.asList("1", "2", "3"));
converter.start();
Assert.assertEquals(1, converter.getMaxDepthPerThrowable());
Assert.assertEquals(2, converter.getShortenedClassNameLength());
Assert.assertEquals(3, converter.getMaxLength());
}
@Test
public void testSuppressed() {
try {
StackTraceElementGenerator.generateSuppressed();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setMaxDepthPerThrowable(8);
String formatted = converter.convert(createEvent(e));
Assert.assertTrue(formatted.contains("Suppressed"));
Assert.assertTrue(formatted.contains("common frames omitted"));
}
}
@Test
public void testShortenedName() {
try {
StackTraceElementGenerator.generateSingle();
Assert.fail();
} catch (RuntimeException e) {
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setMaxDepthPerThrowable(ShortenedThrowableConverter.FULL_MAX_DEPTH_PER_THROWABLE);
converter.setShortenedClassNameLength(10);
String formatted = converter.convert(createEvent(e));
Assert.assertFalse(formatted.contains(getClass().getPackage().getName()));
Assert.assertTrue(formatted.contains("n.l.l.s."));
}
}
private String extractClassAndMethod(String string) {
int atIndex = string.indexOf("at ");
int endIndex = string.indexOf('(');
return string.substring(atIndex + 3, endIndex);
}
private List<String> getLines(String formatted) {
List<String> lines = new ArrayList<String>();
try {
BufferedReader reader = new BufferedReader(new StringReader(formatted));
String line = null;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
return lines;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private int countLines(String formatted) {
return getLines(formatted).size();
}
private ILoggingEvent createEvent(RuntimeException e) {
ILoggingEvent event = mock(ILoggingEvent.class);
when(event.getThrowableProxy()).thenReturn(new ThrowableProxy(e));
return event;
}
}