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 ([email protected]) 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 }