aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/ComparisonFailure.java
blob: d1daa863c3f338d74df3f9bd6e6a83f0b4ae0a9b (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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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.
 * <p/>
 * Inspired by a patch from Alex Chaffee (alex@purpletech.com)
 *
 * @since 4.0
 */
public class ComparisonFailure extends AssertionError {
    /**
     * The maximum length for expected and actual strings. 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;

    /*
     * We have to use the f prefix until the next major release to ensure
     * serialization compatibility. 
     * See https://github.com/junit-team/junit4/issues/976
     */
    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);
        this.fExpected = expected;
        this.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 DIFF_END = "]";
        private static final String DIFF_START = "[";

        /**
         * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When
         * <code>contextLength</code> is exceeded, the Strings are shortened.
         */
        private final int contextLength;
        private final String expected;
        private final String actual;

        /**
         * @param contextLength the maximum length of context surrounding the difference between the compared strings.
         * When context length is exceeded, the prefixes and suffixes are compacted.
         * @param expected the expected string value
         * @param actual the actual string value
         */
        public ComparisonCompactor(int contextLength, String expected, String actual) {
            this.contextLength = contextLength;
            this.expected = expected;
            this.actual = actual;
        }

        public String compact(String message) {
            if (expected == null || actual == null || expected.equals(actual)) {
                return Assert.format(message, expected, actual);
            } else {
                DiffExtractor extractor = new DiffExtractor();
                String compactedPrefix = extractor.compactPrefix();
                String compactedSuffix = extractor.compactSuffix();
                return Assert.format(message,
                        compactedPrefix + extractor.expectedDiff() + compactedSuffix,
                        compactedPrefix + extractor.actualDiff() + compactedSuffix);
            }
        }

        private String sharedPrefix() {
            int end = Math.min(expected.length(), actual.length());
            for (int i = 0; i < end; i++) {
                if (expected.charAt(i) != actual.charAt(i)) {
                    return expected.substring(0, i);
                }
            }
            return expected.substring(0, end);
        }

        private String sharedSuffix(String prefix) {
            int suffixLength = 0;
            int maxSuffixLength = Math.min(expected.length() - prefix.length(),
                    actual.length() - prefix.length()) - 1;
            for (; suffixLength <= maxSuffixLength; suffixLength++) {
                if (expected.charAt(expected.length() - 1 - suffixLength)
                        != actual.charAt(actual.length() - 1 - suffixLength)) {
                    break;
                }
            }
            return expected.substring(expected.length() - suffixLength);
        }

        private class DiffExtractor {
            private final String sharedPrefix;
            private final String sharedSuffix;

            /**
             * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}.
             */
            private DiffExtractor() {
                sharedPrefix = sharedPrefix();
                sharedSuffix = sharedSuffix(sharedPrefix);
            }

            public String expectedDiff() {
                return extractDiff(expected);
            }

            public String actualDiff() {
                return extractDiff(actual);
            }

            public String compactPrefix() {
                if (sharedPrefix.length() <= contextLength) {
                    return sharedPrefix;
                }
                return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength);
            }

            public String compactSuffix() {
                if (sharedSuffix.length() <= contextLength) {
                    return sharedSuffix;
                }
                return sharedSuffix.substring(0, contextLength) + ELLIPSIS;
            }

            private String extractDiff(String source) {
                return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length())
                        + DIFF_END;
            }
        }
    }
}