package org.xtest.interpreter; import java.lang.reflect.Method; import java.util.Map; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.util.PolymorphicDispatcher; import org.eclipse.xtext.xbase.XBlockExpression; import org.eclipse.xtext.xbase.XBooleanLiteral; import org.eclipse.xtext.xbase.XCasePart; import org.eclipse.xtext.xbase.XClosure; import org.eclipse.xtext.xbase.XDoWhileExpression; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XForLoopExpression; import org.eclipse.xtext.xbase.XNullLiteral; import org.eclipse.xtext.xbase.XNumberLiteral; import org.eclipse.xtext.xbase.XStringLiteral; import org.eclipse.xtext.xbase.XTypeLiteral; import org.eclipse.xtext.xbase.XWhileExpression; import org.xtest.XtestUtil; import org.xtest.xTest.XMethodDefExpression; import com.google.common.collect.Iterables; /** * Helper class that builds awesome assertion failed messages that contain the result of each * expression inside a failed assertion expression. * * @author Michael Barry */ @SuppressWarnings("restriction") public class AssertionMessageBuilder { private final PolymorphicDispatcher<Void> traverseDispatcher = PolymorphicDispatcher .createForSingleTarget(new PrefixMethodFilter("_traverse", 3, 3), this); /** * Builds a failed assertion message for the given expression by doing a depth-first traversal * of all of the contained expressions and appending the expression text and the interpreted * result to the output string. * * @param actual * The expression * @param executedExpressions * Map from expression to interpreted result * @return An awesome assertion failure message */ public String buildMessage(XExpression actual, Map<XExpression, Object> executedExpressions) { StringBuilder builder = new StringBuilder("Assertion failed\n"); if (executedExpressions != null) { traverse(actual, executedExpressions, builder); } return builder.toString(); } protected void _traverseBlock(XBlockExpression block, Map<XExpression, Object> results, StringBuilder builder) { // only traverse return expression XExpression last = Iterables.getLast(block.getExpressions(), null); if (last != null) { traverse(last, results, builder); } } protected void _traverseDefault(XExpression actual, Map<XExpression, Object> results, StringBuilder builder) { boolean skipped = appendToString(actual, results, builder); if (!skipped) { traverseChildren(actual, results, builder); } } protected void _traverseDoWhile(XDoWhileExpression expr, Map<XExpression, Object> results, StringBuilder builder) { appendToString(expr, results, builder); } protected void _traverseEObject(EObject obj, Map<XExpression, Object> results, StringBuilder builder) { traverseChildren(obj, results, builder); } protected void _traverseForLoop(XForLoopExpression expr, Map<XExpression, Object> results, StringBuilder builder) { appendToString(expr, results, builder); } protected void _traverseLiteral(XBooleanLiteral expr, Map<XExpression, Object> results, StringBuilder builder) { // skip } protected void _traverseLiteral(XClosure expr, Map<XExpression, Object> results, StringBuilder builder) { // skip } protected void _traverseLiteral(XNullLiteral expr, Map<XExpression, Object> results, StringBuilder builder) { // skip } protected void _traverseLiteral(XNumberLiteral expr, Map<XExpression, Object> results, StringBuilder builder) { // skip } protected void _traverseLiteral(XStringLiteral expr, Map<XExpression, Object> results, StringBuilder builder) { // skip } protected void _traverseLiteral(XTypeLiteral expr, Map<XExpression, Object> results, StringBuilder builder) { // skip } protected void _traverseMethodDef(XMethodDefExpression expr, Map<XExpression, Object> results, StringBuilder builder) { appendToString(expr, results, builder); } protected void _traverseSwitchCase(XCasePart expr, Map<XExpression, Object> results, StringBuilder builder) { // Only dive into switch expressions that were executed if (results.containsKey(expr.getThen())) { traverse(expr.getThen(), results, builder); } } protected void _traverseWhile(XWhileExpression expr, Map<XExpression, Object> results, StringBuilder builder) { appendToString(expr, results, builder); } private boolean appendToString(XExpression actual, Map<XExpression, Object> results, StringBuilder builder) { boolean skipped = !results.containsKey(actual); Object object = results.get(actual); String text = getTrimmedText(actual); builder.append(" \""); builder.append(text); builder.append("\" was "); if (!skipped) { if (object != null) { builder.append(object.getClass().getSimpleName()); builder.append(" <"); builder.append(XtestUtil.trimIfNecessary(object.toString(), 40)); builder.append('>'); } else { builder.append("<null>"); } builder.append('\n'); } else { builder.append("skipped\n"); } return skipped; } private String getTrimmedText(XExpression actual) { return XtestUtil.getText(actual, 60); } /** * Dispatch to the appropriate _traverse* method * * @param expr * The expression * @param results * Map from expression to result * @param builder * result builder */ private void traverse(EObject expr, Map<XExpression, Object> results, StringBuilder builder) { traverseDispatcher.invoke(expr, results, builder); } private void traverseChildren(EObject actual, Map<XExpression, Object> results, StringBuilder builder) { Iterable<EObject> filter = actual.eContents(); for (EObject expression : filter) { traverse(expression, results, builder); } } private static class PrefixMethodFilter extends PolymorphicDispatcher.MethodNameFilter { public PrefixMethodFilter(String prefix, int minParams, int maxParams) { super(prefix, minParams, maxParams); } @Override public boolean apply(Method param) { return param.getName().startsWith(methodName) && param.getParameterTypes().length >= minParams && param.getParameterTypes().length <= maxParams; } } }