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 /*
022 * We have to use the f prefix until the next major release to ensure
023 * serialization compatibility.
024 * See https://github.com/junit-team/junit4/issues/976
025 */
026 private String fExpected;
027 private String fActual;
028
029 /**
030 * Constructs a comparison failure.
031 *
032 * @param message the identifying message or null
033 * @param expected the expected string value
034 * @param actual the actual string value
035 */
036 public ComparisonFailure(String message, String expected, String actual) {
037 super(message);
038 this.fExpected = expected;
039 this.fActual = actual;
040 }
041
042 /**
043 * Returns "..." in place of common prefix and "..." in place of common suffix between expected and actual.
044 *
045 * @see Throwable#getMessage()
046 */
047 @Override
048 public String getMessage() {
049 return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
050 }
051
052 /**
053 * Returns the actual string value
054 *
055 * @return the actual string value
056 */
057 public String getActual() {
058 return fActual;
059 }
060
061 /**
062 * Returns the expected string value
063 *
064 * @return the expected string value
065 */
066 public String getExpected() {
067 return fExpected;
068 }
069
070 private static class ComparisonCompactor {
071 private static final String ELLIPSIS = "...";
072 private static final String DIFF_END = "]";
073 private static final String DIFF_START = "[";
074
075 /**
076 * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When
077 * <code>contextLength</code> is exceeded, the Strings are shortened.
078 */
079 private final int contextLength;
080 private final String expected;
081 private final String actual;
082
083 /**
084 * @param contextLength the maximum length of context surrounding the difference between the compared strings.
085 * When context length is exceeded, the prefixes and suffixes are compacted.
086 * @param expected the expected string value
087 * @param actual the actual string value
088 */
089 public ComparisonCompactor(int contextLength, String expected, String actual) {
090 this.contextLength = contextLength;
091 this.expected = expected;
092 this.actual = actual;
093 }
094
095 public String compact(String message) {
096 if (expected == null || actual == null || expected.equals(actual)) {
097 return Assert.format(message, expected, actual);
098 } else {
099 DiffExtractor extractor = new DiffExtractor();
100 String compactedPrefix = extractor.compactPrefix();
101 String compactedSuffix = extractor.compactSuffix();
102 return Assert.format(message,
103 compactedPrefix + extractor.expectedDiff() + compactedSuffix,
104 compactedPrefix + extractor.actualDiff() + compactedSuffix);
105 }
106 }
107
108 private String sharedPrefix() {
109 int end = Math.min(expected.length(), actual.length());
110 for (int i = 0; i < end; i++) {
111 if (expected.charAt(i) != actual.charAt(i)) {
112 return expected.substring(0, i);
113 }
114 }
115 return expected.substring(0, end);
116 }
117
118 private String sharedSuffix(String prefix) {
119 int suffixLength = 0;
120 int maxSuffixLength = Math.min(expected.length() - prefix.length(),
121 actual.length() - prefix.length()) - 1;
122 for (; suffixLength <= maxSuffixLength; suffixLength++) {
123 if (expected.charAt(expected.length() - 1 - suffixLength)
124 != actual.charAt(actual.length() - 1 - suffixLength)) {
125 break;
126 }
127 }
128 return expected.substring(expected.length() - suffixLength);
129 }
130
131 private class DiffExtractor {
132 private final String sharedPrefix;
133 private final String sharedSuffix;
134
135 /**
136 * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}.
137 */
138 private DiffExtractor() {
139 sharedPrefix = sharedPrefix();
140 sharedSuffix = sharedSuffix(sharedPrefix);
141 }
142
143 public String expectedDiff() {
144 return extractDiff(expected);
145 }
146
147 public String actualDiff() {
148 return extractDiff(actual);
149 }
150
151 public String compactPrefix() {
152 if (sharedPrefix.length() <= contextLength) {
153 return sharedPrefix;
154 }
155 return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength);
156 }
157
158 public String compactSuffix() {
159 if (sharedSuffix.length() <= contextLength) {
160 return sharedSuffix;
161 }
162 return sharedSuffix.substring(0, contextLength) + ELLIPSIS;
163 }
164
165 private String extractDiff(String source) {
166 return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length())
167 + DIFF_END;
168 }
169 }
170 }
171 }