1 //===-- DraftStoreTests.cpp -------------------------------------*- C++ -*-===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "Annotations.h"
11 #include "DraftStore.h"
12 #include "SourceCode.h"
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
15
16 namespace clang {
17 namespace clangd {
18 namespace {
19
20 struct IncrementalTestStep {
21 StringRef Src;
22 StringRef Contents;
23 };
24
rangeLength(StringRef Code,const Range & Rng)25 int rangeLength(StringRef Code, const Range &Rng) {
26 llvm::Expected<size_t> Start = positionToOffset(Code, Rng.start);
27 llvm::Expected<size_t> End = positionToOffset(Code, Rng.end);
28 assert(Start);
29 assert(End);
30 return *End - *Start;
31 }
32
33 /// Send the changes one by one to updateDraft, verify the intermediate results.
stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps)34 void stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps) {
35 DraftStore DS;
36 Annotations InitialSrc(Steps.front().Src);
37 constexpr llvm::StringLiteral Path("/hello.cpp");
38
39 // Set the initial content.
40 DS.addDraft(Path, InitialSrc.code());
41
42 for (size_t i = 1; i < Steps.size(); i++) {
43 Annotations SrcBefore(Steps[i - 1].Src);
44 Annotations SrcAfter(Steps[i].Src);
45 StringRef Contents = Steps[i - 1].Contents;
46 TextDocumentContentChangeEvent Event{
47 SrcBefore.range(),
48 rangeLength(SrcBefore.code(), SrcBefore.range()),
49 Contents.str(),
50 };
51
52 llvm::Expected<std::string> Result = DS.updateDraft(Path, {Event});
53 ASSERT_TRUE(!!Result);
54 EXPECT_EQ(*Result, SrcAfter.code());
55 EXPECT_EQ(*DS.getDraft(Path), SrcAfter.code());
56 }
57 }
58
59 /// Send all the changes at once to updateDraft, check only the final result.
allAtOnce(llvm::ArrayRef<IncrementalTestStep> Steps)60 void allAtOnce(llvm::ArrayRef<IncrementalTestStep> Steps) {
61 DraftStore DS;
62 Annotations InitialSrc(Steps.front().Src);
63 Annotations FinalSrc(Steps.back().Src);
64 constexpr llvm::StringLiteral Path("/hello.cpp");
65 std::vector<TextDocumentContentChangeEvent> Changes;
66
67 for (size_t i = 0; i < Steps.size() - 1; i++) {
68 Annotations Src(Steps[i].Src);
69 StringRef Contents = Steps[i].Contents;
70
71 Changes.push_back({
72 Src.range(),
73 rangeLength(Src.code(), Src.range()),
74 Contents.str(),
75 });
76 }
77
78 // Set the initial content.
79 DS.addDraft(Path, InitialSrc.code());
80
81 llvm::Expected<std::string> Result = DS.updateDraft(Path, Changes);
82
83 ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError());
84 EXPECT_EQ(*Result, FinalSrc.code());
85 EXPECT_EQ(*DS.getDraft(Path), FinalSrc.code());
86 }
87
TEST(DraftStoreIncrementalUpdateTest,Simple)88 TEST(DraftStoreIncrementalUpdateTest, Simple) {
89 // clang-format off
90 IncrementalTestStep Steps[] =
91 {
92 // Replace a range
93 {
94 R"cpp(static int
95 hello[[World]]()
96 {})cpp",
97 "Universe"
98 },
99 // Delete a range
100 {
101 R"cpp(static int
102 hello[[Universe]]()
103 {})cpp",
104 ""
105 },
106 // Add a range
107 {
108 R"cpp(static int
109 hello[[]]()
110 {})cpp",
111 "Monde"
112 },
113 {
114 R"cpp(static int
115 helloMonde()
116 {})cpp",
117 ""
118 }
119 };
120 // clang-format on
121
122 stepByStep(Steps);
123 allAtOnce(Steps);
124 }
125
TEST(DraftStoreIncrementalUpdateTest,MultiLine)126 TEST(DraftStoreIncrementalUpdateTest, MultiLine) {
127 // clang-format off
128 IncrementalTestStep Steps[] =
129 {
130 // Replace a range
131 {
132 R"cpp(static [[int
133 helloWorld]]()
134 {})cpp",
135 R"cpp(char
136 welcome)cpp"
137 },
138 // Delete a range
139 {
140 R"cpp(static char[[
141 welcome]]()
142 {})cpp",
143 ""
144 },
145 // Add a range
146 {
147 R"cpp(static char[[]]()
148 {})cpp",
149 R"cpp(
150 cookies)cpp"
151 },
152 // Replace the whole file
153 {
154 R"cpp([[static char
155 cookies()
156 {}]])cpp",
157 R"cpp(#include <stdio.h>
158 )cpp"
159 },
160 // Delete the whole file
161 {
162 R"cpp([[#include <stdio.h>
163 ]])cpp",
164 "",
165 },
166 // Add something to an empty file
167 {
168 "[[]]",
169 R"cpp(int main() {
170 )cpp",
171 },
172 {
173 R"cpp(int main() {
174 )cpp",
175 ""
176 }
177 };
178 // clang-format on
179
180 stepByStep(Steps);
181 allAtOnce(Steps);
182 }
183
TEST(DraftStoreIncrementalUpdateTest,WrongRangeLength)184 TEST(DraftStoreIncrementalUpdateTest, WrongRangeLength) {
185 DraftStore DS;
186 Path File = "foo.cpp";
187
188 DS.addDraft(File, "int main() {}\n");
189
190 TextDocumentContentChangeEvent Change;
191 Change.range.emplace();
192 Change.range->start.line = 0;
193 Change.range->start.character = 0;
194 Change.range->end.line = 0;
195 Change.range->end.character = 2;
196 Change.rangeLength = 10;
197
198 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
199
200 EXPECT_TRUE(!Result);
201 EXPECT_EQ(
202 llvm::toString(Result.takeError()),
203 "Change's rangeLength (10) doesn't match the computed range length (2).");
204 }
205
TEST(DraftStoreIncrementalUpdateTest,EndBeforeStart)206 TEST(DraftStoreIncrementalUpdateTest, EndBeforeStart) {
207 DraftStore DS;
208 Path File = "foo.cpp";
209
210 DS.addDraft(File, "int main() {}\n");
211
212 TextDocumentContentChangeEvent Change;
213 Change.range.emplace();
214 Change.range->start.line = 0;
215 Change.range->start.character = 5;
216 Change.range->end.line = 0;
217 Change.range->end.character = 3;
218
219 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
220
221 EXPECT_TRUE(!Result);
222 EXPECT_EQ(llvm::toString(Result.takeError()),
223 "Range's end position (0:3) is before start position (0:5)");
224 }
225
TEST(DraftStoreIncrementalUpdateTest,StartCharOutOfRange)226 TEST(DraftStoreIncrementalUpdateTest, StartCharOutOfRange) {
227 DraftStore DS;
228 Path File = "foo.cpp";
229
230 DS.addDraft(File, "int main() {}\n");
231
232 TextDocumentContentChangeEvent Change;
233 Change.range.emplace();
234 Change.range->start.line = 0;
235 Change.range->start.character = 100;
236 Change.range->end.line = 0;
237 Change.range->end.character = 100;
238 Change.text = "foo";
239
240 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
241
242 EXPECT_TRUE(!Result);
243 EXPECT_EQ(llvm::toString(Result.takeError()),
244 "UTF-16 offset 100 is invalid for line 0");
245 }
246
TEST(DraftStoreIncrementalUpdateTest,EndCharOutOfRange)247 TEST(DraftStoreIncrementalUpdateTest, EndCharOutOfRange) {
248 DraftStore DS;
249 Path File = "foo.cpp";
250
251 DS.addDraft(File, "int main() {}\n");
252
253 TextDocumentContentChangeEvent Change;
254 Change.range.emplace();
255 Change.range->start.line = 0;
256 Change.range->start.character = 0;
257 Change.range->end.line = 0;
258 Change.range->end.character = 100;
259 Change.text = "foo";
260
261 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
262
263 EXPECT_TRUE(!Result);
264 EXPECT_EQ(llvm::toString(Result.takeError()),
265 "UTF-16 offset 100 is invalid for line 0");
266 }
267
TEST(DraftStoreIncrementalUpdateTest,StartLineOutOfRange)268 TEST(DraftStoreIncrementalUpdateTest, StartLineOutOfRange) {
269 DraftStore DS;
270 Path File = "foo.cpp";
271
272 DS.addDraft(File, "int main() {}\n");
273
274 TextDocumentContentChangeEvent Change;
275 Change.range.emplace();
276 Change.range->start.line = 100;
277 Change.range->start.character = 0;
278 Change.range->end.line = 100;
279 Change.range->end.character = 0;
280 Change.text = "foo";
281
282 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
283
284 EXPECT_TRUE(!Result);
285 EXPECT_EQ(llvm::toString(Result.takeError()),
286 "Line value is out of range (100)");
287 }
288
TEST(DraftStoreIncrementalUpdateTest,EndLineOutOfRange)289 TEST(DraftStoreIncrementalUpdateTest, EndLineOutOfRange) {
290 DraftStore DS;
291 Path File = "foo.cpp";
292
293 DS.addDraft(File, "int main() {}\n");
294
295 TextDocumentContentChangeEvent Change;
296 Change.range.emplace();
297 Change.range->start.line = 0;
298 Change.range->start.character = 0;
299 Change.range->end.line = 100;
300 Change.range->end.character = 0;
301 Change.text = "foo";
302
303 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change});
304
305 EXPECT_TRUE(!Result);
306 EXPECT_EQ(llvm::toString(Result.takeError()),
307 "Line value is out of range (100)");
308 }
309
310 /// Check that if a valid change is followed by an invalid change, the original
311 /// version of the document (prior to all changes) is kept.
TEST(DraftStoreIncrementalUpdateTest,InvalidRangeInASequence)312 TEST(DraftStoreIncrementalUpdateTest, InvalidRangeInASequence) {
313 DraftStore DS;
314 Path File = "foo.cpp";
315
316 StringRef OriginalContents = "int main() {}\n";
317 DS.addDraft(File, OriginalContents);
318
319 // The valid change
320 TextDocumentContentChangeEvent Change1;
321 Change1.range.emplace();
322 Change1.range->start.line = 0;
323 Change1.range->start.character = 0;
324 Change1.range->end.line = 0;
325 Change1.range->end.character = 0;
326 Change1.text = "Hello ";
327
328 // The invalid change
329 TextDocumentContentChangeEvent Change2;
330 Change2.range.emplace();
331 Change2.range->start.line = 0;
332 Change2.range->start.character = 5;
333 Change2.range->end.line = 0;
334 Change2.range->end.character = 100;
335 Change2.text = "something";
336
337 llvm::Expected<std::string> Result = DS.updateDraft(File, {Change1, Change2});
338
339 EXPECT_TRUE(!Result);
340 EXPECT_EQ(llvm::toString(Result.takeError()),
341 "UTF-16 offset 100 is invalid for line 0");
342
343 llvm::Optional<std::string> Contents = DS.getDraft(File);
344 EXPECT_TRUE(Contents);
345 EXPECT_EQ(*Contents, OriginalContents);
346 }
347
348 } // namespace
349 } // namespace clangd
350 } // namespace clang
351