aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/ComparisonFailure.java
blob: d37db4fc3af12a505179076cc59bdbe1f405404a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package org.junit;

/**
 * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. Create and throw
 * a <code>ComparisonFailure</code> manually if you want to show users the difference between two complex 
 * strings.
 * 
 * Inspired by a patch from Alex Chaffee (alex@purpletech.com)
 */
public class ComparisonFailure extends AssertionError {	
	/** 
	 * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened. 
	 * @see ComparisonCompactor
	 */
	private static final int MAX_CONTEXT_LENGTH= 20;
	private static final long serialVersionUID= 1L;
	
	private String fExpected;
	private String fActual;

	/**
	 * Constructs a comparison failure.
	 * @param message the identifying message or null
	 * @param expected the expected string value
	 * @param actual the actual string value
	 */
	public ComparisonFailure (String message, String expected, String actual) {
		super (message);
		fExpected= expected;
		fActual= actual;
	}
	
	/**
	 * Returns "..." in place of common prefix and "..." in
	 * place of common suffix between expected and actual.
	 * 
	 * @see Throwable#getMessage()
	 */
	@Override
	public String getMessage() {
		return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
	}
	
	/**
	 * Returns the actual string value
	 * @return the actual string value
	 */
	public String getActual() {
		return fActual;
	}
	/**
	 * Returns the expected string value
	 * @return the expected string value
	 */
	public String getExpected() {
		return fExpected;
	}
	
	private static class ComparisonCompactor {
		private static final String ELLIPSIS= "...";
		private static final String DELTA_END= "]";
		private static final String DELTA_START= "[";
		
		/**
		 * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code> 
		 * is exceeded, the Strings are shortened
		 */
		private int fContextLength;
		private String fExpected;
		private String fActual;
		private int fPrefix;
		private int fSuffix;

		/**
		 * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength 
		 * is exceeded, the Strings are shortened
		 * @param expected the expected string value
		 * @param actual the actual string value
		 */
		public ComparisonCompactor(int contextLength, String expected, String actual) {
			fContextLength= contextLength;
			fExpected= expected;
			fActual= actual;
		}

		private String compact(String message) {
			if (fExpected == null || fActual == null || areStringsEqual())
				return Assert.format(message, fExpected, fActual);

			findCommonPrefix();
			findCommonSuffix();
			String expected= compactString(fExpected);
			String actual= compactString(fActual);
			return Assert.format(message, expected, actual);
		}

		private String compactString(String source) {
			String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
			if (fPrefix > 0)
				result= computeCommonPrefix() + result;
			if (fSuffix > 0)
				result= result + computeCommonSuffix();
			return result;
		}

		private void findCommonPrefix() {
			fPrefix= 0;
			int end= Math.min(fExpected.length(), fActual.length());
			for (; fPrefix < end; fPrefix++) {
				if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix))
					break;
			}
		}

		private void findCommonSuffix() {
			int expectedSuffix= fExpected.length() - 1;
			int actualSuffix= fActual.length() - 1;
			for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) {
				if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix))
					break;
			}
			fSuffix=  fExpected.length() - expectedSuffix;
		}

		private String computeCommonPrefix() {
			return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
		}

		private String computeCommonSuffix() {
			int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length());
			return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : "");
		}

		private boolean areStringsEqual() {
			return fExpected.equals(fActual);
		}
	}
}