    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    }