001 package org.junit; 002 003 /** 004 * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. 005 * Create and throw a <code>ComparisonFailure</code> manually if you want to show users the 006 * difference between two complex strings. 007 * <p/> 008 * Inspired by a patch from Alex Chaffee (alex@purpletech.com) 009 * 010 * @since 4.0 011 */ 012 public class ComparisonFailure extends AssertionError { 013 /** 014 * The maximum length for expected and actual strings. If it is exceeded, the strings should be shortened. 015 * 016 * @see ComparisonCompactor 017 */ 018 private static final int MAX_CONTEXT_LENGTH = 20; 019 private static final long serialVersionUID = 1L; 020 021 private String expected; 022 private String actual; 023 024 /** 025 * Constructs a comparison failure. 026 * 027 * @param message the identifying message or null 028 * @param expected the expected string value 029 * @param actual the actual string value 030 */ 031 public ComparisonFailure(String message, String expected, String actual) { 032 super(message); 033 this.expected = expected; 034 this.actual = actual; 035 } 036 037 /** 038 * Returns "..." in place of common prefix and "..." in place of common suffix between expected and actual. 039 * 040 * @see Throwable#getMessage() 041 */ 042 @Override 043 public String getMessage() { 044 return new ComparisonCompactor(MAX_CONTEXT_LENGTH, expected, actual).compact(super.getMessage()); 045 } 046 047 /** 048 * Returns the actual string value 049 * 050 * @return the actual string value 051 */ 052 public String getActual() { 053 return actual; 054 } 055 056 /** 057 * Returns the expected string value 058 * 059 * @return the expected string value 060 */ 061 public String getExpected() { 062 return expected; 063 } 064 065 private static class ComparisonCompactor { 066 private static final String ELLIPSIS = "..."; 067 private static final String DIFF_END = "]"; 068 private static final String DIFF_START = "["; 069 070 /** 071 * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When 072 * <code>contextLength</code> is exceeded, the Strings are shortened. 073 */ 074 private final int contextLength; 075 private final String expected; 076 private final String actual; 077 078 /** 079 * @param contextLength the maximum length of context surrounding the difference between the compared strings. 080 * When context length is exceeded, the prefixes and suffixes are compacted. 081 * @param expected the expected string value 082 * @param actual the actual string value 083 */ 084 public ComparisonCompactor(int contextLength, String expected, String actual) { 085 this.contextLength = contextLength; 086 this.expected = expected; 087 this.actual = actual; 088 } 089 090 public String compact(String message) { 091 if (expected == null || actual == null || expected.equals(actual)) { 092 return Assert.format(message, expected, actual); 093 } else { 094 DiffExtractor extractor = new DiffExtractor(); 095 String compactedPrefix = extractor.compactPrefix(); 096 String compactedSuffix = extractor.compactSuffix(); 097 return Assert.format(message, 098 compactedPrefix + extractor.expectedDiff() + compactedSuffix, 099 compactedPrefix + extractor.actualDiff() + compactedSuffix); 100 } 101 } 102 103 private String sharedPrefix() { 104 int end = Math.min(expected.length(), actual.length()); 105 for (int i = 0; i < end; i++) { 106 if (expected.charAt(i) != actual.charAt(i)) { 107 return expected.substring(0, i); 108 } 109 } 110 return expected.substring(0, end); 111 } 112 113 private String sharedSuffix(String prefix) { 114 int suffixLength = 0; 115 int maxSuffixLength = Math.min(expected.length() - prefix.length(), 116 actual.length() - prefix.length()) - 1; 117 for (; suffixLength <= maxSuffixLength; suffixLength++) { 118 if (expected.charAt(expected.length() - 1 - suffixLength) 119 != actual.charAt(actual.length() - 1 - suffixLength)) { 120 break; 121 } 122 } 123 return expected.substring(expected.length() - suffixLength); 124 } 125 126 private class DiffExtractor { 127 private final String sharedPrefix; 128 private final String sharedSuffix; 129 130 /** 131 * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}. 132 */ 133 private DiffExtractor() { 134 sharedPrefix = sharedPrefix(); 135 sharedSuffix = sharedSuffix(sharedPrefix); 136 } 137 138 public String expectedDiff() { 139 return extractDiff(expected); 140 } 141 142 public String actualDiff() { 143 return extractDiff(actual); 144 } 145 146 public String compactPrefix() { 147 if (sharedPrefix.length() <= contextLength) { 148 return sharedPrefix; 149 } 150 return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength); 151 } 152 153 public String compactSuffix() { 154 if (sharedSuffix.length() <= contextLength) { 155 return sharedSuffix; 156 } 157 return sharedSuffix.substring(0, contextLength) + ELLIPSIS; 158 } 159 160 private String extractDiff(String source) { 161 return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length()) 162 + DIFF_END; 163 } 164 } 165 } 166 }