blob: 74439754a6474af0e231da4cce04e4f1be1dbc89 [file] [log] [blame]
Simon Marchi98082622018-03-26 14:41:40 +00001//===-- 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
16namespace clang {
17namespace clangd {
18namespace {
19
20struct IncrementalTestStep {
21 StringRef Src;
22 StringRef Contents;
23};
24
25int 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.
34void 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.
60void 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
88TEST(DraftStoreIncrementalUpdateTest, Simple) {
89 // clang-format off
90 IncrementalTestStep Steps[] =
91 {
92 // Replace a range
93 {
94R"cpp(static int
95hello[[World]]()
96{})cpp",
97 "Universe"
98 },
99 // Delete a range
100 {
101R"cpp(static int
102hello[[Universe]]()
103{})cpp",
104 ""
105 },
106 // Add a range
107 {
108R"cpp(static int
109hello[[]]()
110{})cpp",
111 "Monde"
112 },
113 {
114R"cpp(static int
115helloMonde()
116{})cpp",
117 ""
118 }
119 };
120 // clang-format on
121
122 stepByStep(Steps);
123 allAtOnce(Steps);
124}
125
126TEST(DraftStoreIncrementalUpdateTest, MultiLine) {
127 // clang-format off
128 IncrementalTestStep Steps[] =
129 {
130 // Replace a range
131 {
132R"cpp(static [[int
133helloWorld]]()
134{})cpp",
135R"cpp(char
136welcome)cpp"
137 },
138 // Delete a range
139 {
140R"cpp(static char[[
141welcome]]()
142{})cpp",
143 ""
144 },
145 // Add a range
146 {
147R"cpp(static char[[]]()
148{})cpp",
149 R"cpp(
150cookies)cpp"
151 },
152 // Replace the whole file
153 {
154R"cpp([[static char
155cookies()
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
184TEST(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
206TEST(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
226TEST(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 "Character value is out of range (100)");
245}
246
247TEST(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 "Character value is out of range (100)");
266}
267
268TEST(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
289TEST(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.
312TEST(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 "Character value is out of range (100)");
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