1 //          Copyright Mario Kröplin 2017.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 module dunit.diff;
7 
8 import std.algorithm;
9 import std.range;
10 import std.typecons;
11 
12 /**
13  * Returns a description of the difference between the strings.
14  */
15 string description(string expected, string actual) @safe pure
16 {
17     const MAX_LENGTH = 20;
18     const result = diff(expected, actual);
19     const oneLiner = max(result[0].length, result[1].length) <= MAX_LENGTH
20             && !result[0].canFind("\n", "\r")
21             && !result[1].canFind("\n", "\r");
22 
23     if (oneLiner)
24         return "expected: <" ~ result[0] ~ "> but was: <" ~ result[1] ~ ">";
25     else
26         return "expected:\n" ~ result[0] ~ "\nbut was:\n" ~ result[1];
27 }
28 
29 ///
30 @safe pure unittest
31 {
32     assert(description("ab", "Ab") == "expected: <<a>b> but was: <<A>b>");
33     assert(description("a\nb", "A\nb") == "expected:\n<a>\nb\nbut was:\n<A>\nb");
34 }
35 
36 /**
37  * Returns a pair of strings that highlight the difference between lhs and rhs.
38  */
39 Tuple!(string, string) diff(string)(string lhs, string rhs)
40 {
41     const MAX_LENGTH = 20;
42 
43     if (lhs == rhs)
44         return tuple(lhs, rhs);
45 
46     auto rest = mismatch(lhs, rhs);
47     auto retroDiff = mismatch(retro(rest[0]), retro(rest[1]));
48     auto diff = tuple(retro(retroDiff[0]), retro(retroDiff[1]));
49     string prefix = lhs[0 .. $ - rest[0].length];
50     string suffix = lhs[prefix.length + diff[0].length .. $];
51 
52     if (prefix.length > MAX_LENGTH)
53         prefix = "..." ~ prefix[$ - MAX_LENGTH .. $];
54     if (suffix.length > MAX_LENGTH)
55         suffix = suffix[0 .. MAX_LENGTH] ~ "...";
56 
57     return tuple(
58             prefix ~ '<' ~ diff[0] ~ '>' ~ suffix,
59             prefix ~ '<' ~ diff[1] ~ '>' ~ suffix);
60 }
61 
62 ///
63 @safe pure unittest
64 {
65     assert(diff("abc", "abc") == tuple("abc", "abc"));
66     // highlight difference
67     assert(diff("abc", "Abc") == tuple("<a>bc", "<A>bc"));
68     assert(diff("abc", "aBc") == tuple("a<b>c", "a<B>c"));
69     assert(diff("abc", "abC") == tuple("ab<c>", "ab<C>"));
70     assert(diff("abc", "") == tuple("<abc>", "<>"));
71     assert(diff("abc", "abbc") == tuple("ab<>c", "ab<b>c"));
72     // abbreviate long prefix or suffix
73     assert(diff("_12345678901234567890a", "_12345678901234567890A")
74             == tuple("...12345678901234567890<a>", "...12345678901234567890<A>"));
75     assert(diff("a12345678901234567890_", "A12345678901234567890_")
76             == tuple("<a>12345678901234567890...", "<A>12345678901234567890..."));
77 }