Skip to content

Commit

Permalink
extended stack trace filtering (#873)
Browse files Browse the repository at this point in the history
- remove assertj related elements from stack trace of error thrown by
failWithMessage(...)
- also remove all elements of custom assert subclasses
  • Loading branch information
JulianJho authored and joel-costigliola committed Feb 12, 2017
1 parent c7d3221 commit 00b5540
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 6 deletions.
49 changes: 44 additions & 5 deletions src/main/java/org/assertj/core/api/AbstractAssert.java
Expand Up @@ -12,6 +12,7 @@
*/
package org.assertj.core.api;

import static org.assertj.core.util.Lists.newArrayList;
import static org.assertj.core.util.Strings.formatIfArgs;

import java.util.Comparator;
Expand Down Expand Up @@ -44,6 +45,8 @@
*/
public abstract class AbstractAssert<SELF extends AbstractAssert<SELF, ACTUAL>, ACTUAL> implements Assert<SELF, ACTUAL> {

private static final String ORG_ASSERTJ = "org.assert";

@VisibleForTesting
Objects objects = Objects.instance();

Expand Down Expand Up @@ -106,10 +109,15 @@ public WritableAssertionInfo getWritableAssertionInfo() {
* @param arguments the arguments referenced by the format specifiers in the errorMessage string.
*/
protected void failWithMessage(String errorMessage, Object... arguments) {
AssertionError failureWithOverriddenErrorMessage = Failures.instance().failureIfErrorMessageIsOverridden(info);
if (failureWithOverriddenErrorMessage != null) throw failureWithOverriddenErrorMessage;
String description = MessageFormatter.instance().format(info.description(), info.representation(), "");
throw new AssertionError(description + String.format(errorMessage, arguments));
AssertionError assertionError = Failures.instance().failureIfErrorMessageIsOverridden(info);
if (assertionError == null) {
// error message was not overridden, build it.
String description = MessageFormatter.instance().format(info.description(), info.representation(), "");
assertionError = new AssertionError(description + String.format(errorMessage, arguments));
}
Failures.instance().removeAssertJRelatedElementsFromStackTraceIfNeeded(assertionError);
removeCustomAssertRelatedElementsFromStackTraceIfNeeded(assertionError);
throw assertionError;
}

/**
Expand All @@ -125,7 +133,38 @@ protected void failWithMessage(String errorMessage, Object... arguments) {
* @throws an {@link AssertionError} with a message corresponding to the given {@link BasicErrorMessageFactory}.
*/
protected void throwAssertionError(ErrorMessageFactory errorMessageFactory) {
throw Failures.instance().failure(info, errorMessageFactory);
AssertionError failure = Failures.instance().failure(info, errorMessageFactory);
removeCustomAssertRelatedElementsFromStackTraceIfNeeded(failure);
throw failure;
}

private void removeCustomAssertRelatedElementsFromStackTraceIfNeeded(AssertionError assertionError) {
if (!Failures.instance().isRemoveAssertJRelatedElementsFromStackTrace()) return;
if (isAssertjAssertClass()) return;

List<StackTraceElement> filtered = newArrayList(assertionError.getStackTrace());
for (StackTraceElement element : assertionError.getStackTrace()) {
if (isElementOfCustomAssert(element)) {
filtered.remove(element);
}
}
StackTraceElement[] newStackTrace = filtered.toArray(new StackTraceElement[filtered.size()]);
assertionError.setStackTrace(newStackTrace);
}

private boolean isAssertjAssertClass() {
return getClass().getName().startsWith(ORG_ASSERTJ);
}

private boolean isElementOfCustomAssert(StackTraceElement stackTraceElement) {
Class<?> currentAssertClass = getClass();
while (currentAssertClass != AbstractAssert.class) {
if (stackTraceElement.getClassName().equals(currentAssertClass.getName())) {
return true;
}
currentAssertClass = currentAssertClass.getSuperclass();
}
return false;
}

/** {@inheritDoc} */
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/assertj/core/internal/Failures.java
Expand Up @@ -68,6 +68,11 @@ public void setRemoveAssertJRelatedElementsFromStackTrace(boolean removeAssertJR
this.removeAssertJRelatedElementsFromStackTrace = removeAssertJRelatedElementsFromStackTrace;
}

/** Returns whether or not we remove elements related to AssertJ from assertion error stack trace. */
public boolean isRemoveAssertJRelatedElementsFromStackTrace() {
return removeAssertJRelatedElementsFromStackTrace;
}

@VisibleForTesting
Failures() {}

Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/assertj/core/util/Throwables.java
Expand Up @@ -16,7 +16,8 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.ArrayList;
import java.util.List;

/**
* Utility methods related to <code>{@link Throwable}</code>s.
Expand Down Expand Up @@ -107,6 +108,7 @@ public static void removeAssertJRelatedElementsFromStackTrace(Throwable throwabl
throwable.setStackTrace(newStackTrace);
}


/**
* Get the root cause (ie the last non null cause) from a {@link Throwable}.
*
Expand Down
@@ -0,0 +1,116 @@
/**
* 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.
*
* Copyright 2012-2017 the original author or authors.
*/
package org.example.custom;

import static org.assertj.core.api.Assertions.assertThat;

import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.Condition;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.assertj.core.internal.Failures;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class CustomAsserts_filter_stacktrace_Test {

@Test
public void should_filter_when_custom_assert_fails_with_message() {
try {
new CustomAssert("").fail();
} catch (AssertionError e) {
assertThat(e.getStackTrace()).areNot(elementOf(CustomAssert.class));
}
}

@Test
public void should_filter_when_custom_assert_fails_with_overridden_message() {
try {
new CustomAssert("").overridingErrorMessage("overridden message").fail();
} catch (AssertionError e) {
assertThat(e.getStackTrace()).areNot(elementOf(CustomAssert.class));
}
}

@Test
public void should_filter_when_custom_assert_throws_assertion_error() {
try {
new CustomAssert("").throwAnAssertionError();
} catch (AssertionError e) {
assertThat(e.getStackTrace()).areNot(elementOf(CustomAssert.class));
}
}

@Test
public void should_filter_when_abstract_custom_assert_fails() {
try {
new CustomAssert("").failInAbstractAssert();
} catch (AssertionError e) {
assertThat(e.getStackTrace()).areNot(elementOf(CustomAbstractAssert.class));
}
}

@Test
public void should_not_filter_when_global_remove_option_is_disabled() {
Failures.instance().setRemoveAssertJRelatedElementsFromStackTrace(false);
try {
new CustomAssert("").fail();
} catch (AssertionError e) {
assertThat(e.getStackTrace()).areAtLeastOne(elementOf(CustomAssert.class));
}
}

@Before
@After
public void enableStackTraceFiltering() {
Failures.instance().setRemoveAssertJRelatedElementsFromStackTrace(true);
}

private static Condition<StackTraceElement> elementOf(final Class<?> clazz) {
return new Condition<StackTraceElement>("StackTraceElement of " + clazz) {
@Override
public boolean matches(StackTraceElement value) {
return value.getClassName().equals(clazz.getName());
}
};
}

private static class CustomAssert extends CustomAbstractAssert<CustomAssert> {

public CustomAssert(String actual) {
super(actual, CustomAssert.class);
}

public CustomAssert fail() {
failWithMessage("failing assert");
return this;
}

public CustomAssert throwAnAssertionError() {
throwAssertionError(new BasicErrorMessageFactory("failing assert"));
return this;
}
}

private static class CustomAbstractAssert<S extends CustomAbstractAssert<S>> extends AbstractObjectAssert<S, String> {

protected CustomAbstractAssert(String actual, Class<?> selfType) {
super(actual, selfType);
}

public S failInAbstractAssert() {
failWithMessage("failing assert");
return myself;
}
}
}

0 comments on commit 00b5540

Please sign in to comment.