/* * Copyright 2012 the original author or authors. * * 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 org.gradle.api.internal.tasks.testing.logging; import com.google.common.base.Strings; import com.google.common.collect.Lists; import org.gradle.api.Nullable; import org.gradle.api.specs.AndSpec; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.testing.TestDescriptor; import org.gradle.api.tasks.testing.logging.TestLogging; import org.gradle.api.tasks.testing.logging.TestStackTraceFilter; import org.gradle.util.TextUtil; import java.util.List; public class FullExceptionFormatter implements TestExceptionFormatter { private static final String INDENT = " "; private final TestLogging testLogging; public FullExceptionFormatter(TestLogging testLogging) { this.testLogging = testLogging; } @Override public String format(TestDescriptor descriptor, List<Throwable> exceptions) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < exceptions.size(); i++) { printException(descriptor, exceptions.get(i), null, 0, builder); if (i < exceptions.size() - 1) { builder.append('\n'); } } return builder.toString(); } private void printException(TestDescriptor descriptor, Throwable exception, @Nullable List<StackTraceElement> parentTrace, int exceptionLevel, StringBuilder builder) { String exceptionIndent = Strings.repeat(INDENT, exceptionLevel + 1); String exceptionText = exceptionLevel == 0 ? exception.toString() : "\nCaused by:\n" + exception.toString(); String indentedText = TextUtil.indent(exceptionText, exceptionIndent); builder.append(indentedText); builder.append('\n'); String stackTraceIndent = exceptionIndent + INDENT; List<StackTraceElement> stackTrace = null; if (testLogging.getShowStackTraces()) { stackTrace = filterStackTrace(exception, descriptor); int commonElements = countCommonElements(stackTrace, parentTrace); for (int i = 0; i < stackTrace.size() - commonElements; i++) { builder.append(stackTraceIndent); builder.append("at "); builder.append(stackTrace.get(i)); builder.append('\n'); } if (commonElements != 0) { builder.append(stackTraceIndent); builder.append("... "); builder.append(commonElements); builder.append(" more"); builder.append('\n'); } } if (testLogging.getShowCauses() && exception.getCause() != null) { printException(descriptor, exception.getCause(), stackTrace, exceptionLevel + 1, builder); } } private List<StackTraceElement> filterStackTrace(Throwable exception, TestDescriptor descriptor) { Spec<StackTraceElement> filterSpec = createCompositeFilter(descriptor); StackTraceFilter filter = new StackTraceFilter(filterSpec); return filter.filter(exception); } private Spec<StackTraceElement> createCompositeFilter(TestDescriptor descriptor) { List<Spec<StackTraceElement>> filters = Lists.newArrayList(); for (TestStackTraceFilter type : testLogging.getStackTraceFilters()) { filters.add(createFilter(descriptor, type)); } return new AndSpec<StackTraceElement>(filters); } private Spec<StackTraceElement> createFilter(TestDescriptor descriptor, TestStackTraceFilter filterType) { switch (filterType) { case ENTRY_POINT: return new ClassMethodNameStackTraceSpec(descriptor.getClassName(), descriptor.getName()); case TRUNCATE: return new TruncatedStackTraceSpec(new ClassMethodNameStackTraceSpec(descriptor.getClassName(), null)); case GROOVY: return new GroovyStackTraceSpec(); default: throw new AssertionError(); } } private int countCommonElements(List<StackTraceElement> stackTrace, @Nullable List<StackTraceElement> parentTrace) { if (parentTrace == null) { return 0; } int commonElements = 0; for (int i = stackTrace.size() - 1, j = parentTrace.size() - 1; // i >= 1 makes sure that commonElements < stackTrace.size() i >= 1 && j >= 0 && stackTrace.get(i).equals(parentTrace.get(j)); i--, j--) { commonElements++; } return commonElements; } }