1 module tests.support.Util;
2 
3 bool fileExists(string path)
4 {
5     import std.file : exists, isFile;
6     return exists(path) && isFile(path);
7 }
8 
9 string mismatchRegion(
10     string expected,
11     string actual,
12     size_t margin,
13     bool strict,
14     string prefix = "<<<<<<< expected",
15     string interfix = "=======",
16     string suffix = ">>>>>>> actual")
17 {
18     import std.algorithm.iteration : splitter;
19     import std.range : empty;
20     import std..string : lineSplitter, stripRight, strip;
21     import std.algorithm.comparison : min;
22 
23     if (!strict)
24     {
25         expected = stripRight(expected);
26         actual = stripRight(actual);
27     }
28 
29     string[] Q;
30     size_t q = 0;
31     size_t p = 0;
32     Q.length = margin;
33 
34     size_t line = 0;
35 
36     auto aItr = lineSplitter(expected);
37     auto bItr = lineSplitter(actual);
38 
39     while (!aItr.empty && !bItr.empty)
40     {
41         if (aItr.front != bItr.front)
42             break;
43 
44         Q[p] = aItr.front;
45 
46         q = min(q + 1, margin);
47         p = (p + 1) % margin;
48 
49         aItr.popFront();
50         bItr.popFront();
51 
52         ++line;
53     }
54 
55     if (strict && expected.length != actual.length && aItr.empty && bItr.empty)
56     {
57         if (expected.length < actual.length)
58             bItr = lineSplitter("\n");
59         else
60             aItr = lineSplitter("\n");
61     }
62 
63     margin = expected.strip.empty
64         || actual.strip.empty
65         ? size_t.max : margin;
66 
67     if (!aItr.empty || !bItr.empty)
68     {
69         import std.array : Appender;
70         import std.conv : to;
71 
72         auto result = Appender!string();
73 
74         auto l = line - q;
75 
76         result.put(prefix);
77         result.put("\n");
78 
79         foreach (i; 0 .. q)
80         {
81             result.put(to!string(l + i));
82             result.put(": ");
83             result.put(Q[(p + i) % q]);
84             result.put("\n");
85         }
86 
87         for (size_t i = 0; i <= margin && !aItr.empty; ++i)
88         {
89             result.put(to!string(line + i));
90             result.put("> ");
91             result.put(aItr.front);
92             result.put("\n");
93             aItr.popFront();
94         }
95 
96         result.put(interfix);
97         result.put("\n");
98 
99         foreach (i; 0 .. q)
100         {
101             result.put(to!string(l + i));
102             result.put(": ");
103             result.put(Q[(p + i) % q]);
104             result.put("\n");
105         }
106 
107         for (size_t i = 0; i <= margin && !bItr.empty; ++i)
108         {
109             result.put(to!string(line + i));
110             result.put("> ");
111             result.put(bItr.front);
112             result.put("\n");
113             bItr.popFront();
114         }
115 
116         result.put(suffix);
117         result.put("\n");
118 
119         return result.data;
120     }
121 
122     return null;
123 }
124 
125 string mismatchRegionTranslated(
126     string translated,
127     string expected,
128     size_t margin,
129     bool strict)
130 {
131     return mismatchRegion(
132         translated,
133         expected,
134         margin,
135         strict,
136         "Translated code doesn't match expected.\n<<<<<<< translated",
137         "=======",
138         ">>>>>>> expected");
139 }
140 
141 unittest
142 {
143     import core.exception : AssertError;
144 
145     void assertMismatchRegion(
146         string expected,
147         string a,
148         string b,
149         bool strict = false,
150         size_t margin = 2,
151         string file = __FILE__,
152         size_t line = __LINE__)
153     {
154         import std.format;
155 
156         auto actual = mismatchRegion(a, b, margin, strict);
157 
158         if (expected != actual)
159         {
160             auto templ = "\nExpected:\n%s\nActual:\n%s\n";
161 
162             string message = format(templ, expected, actual);
163 
164             throw new AssertError(message, file, line);
165         }
166     }
167 
168     assertMismatchRegion(null, "", "");
169 
170     assertMismatchRegion(null, "foo", "foo");
171 
172     assertMismatchRegion(q"X
173 <<<<<<< expected
174 0: foo
175 1> bar
176 =======
177 0: foo
178 1> baz
179 >>>>>>> actual
180 X", "foo\nbar", "foo\nbaz");
181 
182     assertMismatchRegion(q"X
183 <<<<<<< expected
184 0: foo
185 =======
186 0: foo
187 1> baz
188 >>>>>>> actual
189 X", "foo", "foo\nbaz");
190 
191     assertMismatchRegion(q"X
192 <<<<<<< expected
193 1: bar
194 2: baz
195 3> quuux
196 4> yada
197 5> yada
198 =======
199 1: bar
200 2: baz
201 3> quux
202 4> yada
203 5> yada
204 >>>>>>> actual
205 X", "foo\nbar\nbaz\nquuux\nyada\nyada\nyada\nlast", "foo\nbar\nbaz\nquux\nyada\nyada\nyada\nlast");
206 
207     assertMismatchRegion(q"X
208 <<<<<<< expected
209 1: bar
210 2: baz
211 3> quuux
212 4> yada
213 5> yada
214 =======
215 1: bar
216 2: baz
217 3> quuuux
218 4> yada
219 5> yada
220 >>>>>>> actual
221 X", "foo\nbar\nbaz\nquuux\nyada\nyada\nyada\nlast", "foo\nbar\nbaz\nquuuux\nyada\nyada\nyada\nlast");
222 
223     assertMismatchRegion(
224         "<<<<<<< expected\n0: foo\n1> \n=======\n0: foo\n>>>>>>> actual\n",
225         "foo\n",
226         "foo",
227         true);
228 }