clang-tools  12.0.0git
DraftStoreTests.cpp
Go to the documentation of this file.
1 //===-- DraftStoreTests.cpp -------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "Annotations.h"
10 #include "DraftStore.h"
11 #include "SourceCode.h"
12 #include "llvm/Testing/Support/Error.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  llvm::StringRef Src;
22  llvm::StringRef Contents;
23 };
24 
25 int rangeLength(llvm::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.
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  EXPECT_EQ(0, DS.addDraft(Path, llvm::None, 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  llvm::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<DraftStore::Draft> Result =
53  DS.updateDraft(Path, llvm::None, {Event});
54  ASSERT_TRUE(!!Result);
55  EXPECT_EQ(Result->Contents, SrcAfter.code());
56  EXPECT_EQ(DS.getDraft(Path)->Contents, SrcAfter.code());
57  EXPECT_EQ(Result->Version, static_cast<int64_t>(i));
58  }
59 }
60 
61 /// Send all the changes at once to updateDraft, check only the final result.
62 void allAtOnce(llvm::ArrayRef<IncrementalTestStep> Steps) {
63  DraftStore DS;
64  Annotations InitialSrc(Steps.front().Src);
65  Annotations FinalSrc(Steps.back().Src);
66  constexpr llvm::StringLiteral Path("/hello.cpp");
67  std::vector<TextDocumentContentChangeEvent> Changes;
68 
69  for (size_t i = 0; i < Steps.size() - 1; i++) {
70  Annotations Src(Steps[i].Src);
71  llvm::StringRef Contents = Steps[i].Contents;
72 
73  Changes.push_back({
74  Src.range(),
75  rangeLength(Src.code(), Src.range()),
76  Contents.str(),
77  });
78  }
79 
80  // Set the initial content.
81  EXPECT_EQ(0, DS.addDraft(Path, llvm::None, InitialSrc.code()));
82 
83  llvm::Expected<DraftStore::Draft> Result =
84  DS.updateDraft(Path, llvm::None, Changes);
85 
86  ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError());
87  EXPECT_EQ(Result->Contents, FinalSrc.code());
88  EXPECT_EQ(DS.getDraft(Path)->Contents, FinalSrc.code());
89  EXPECT_EQ(Result->Version, 1);
90 }
91 
92 TEST(DraftStoreIncrementalUpdateTest, Simple) {
93  // clang-format off
94  IncrementalTestStep Steps[] =
95  {
96  // Replace a range
97  {
98 R"cpp(static int
99 hello[[World]]()
100 {})cpp",
101  "Universe"
102  },
103  // Delete a range
104  {
105 R"cpp(static int
106 hello[[Universe]]()
107 {})cpp",
108  ""
109  },
110  // Add a range
111  {
112 R"cpp(static int
113 hello[[]]()
114 {})cpp",
115  "Monde"
116  },
117  {
118 R"cpp(static int
119 helloMonde()
120 {})cpp",
121  ""
122  }
123  };
124  // clang-format on
125 
126  stepByStep(Steps);
127  allAtOnce(Steps);
128 }
129 
130 TEST(DraftStoreIncrementalUpdateTest, MultiLine) {
131  // clang-format off
132  IncrementalTestStep Steps[] =
133  {
134  // Replace a range
135  {
136 R"cpp(static [[int
137 helloWorld]]()
138 {})cpp", R"cpp(char
139 welcome)cpp"
140  },
141  // Delete a range
142  {
143 R"cpp(static char[[
144 welcome]]()
145 {})cpp",
146  ""
147  },
148  // Add a range
149  {
150 R"cpp(static char[[]]()
151 {})cpp",
152  R"cpp(
153 cookies)cpp"
154  },
155  // Replace the whole file
156  {
157 R"cpp([[static char
158 cookies()
159 {}]])cpp",
160  R"cpp(#include <stdio.h>
161 )cpp"
162  },
163  // Delete the whole file
164  {
165  R"cpp([[#include <stdio.h>
166 ]])cpp",
167  "",
168  },
169  // Add something to an empty file
170  {
171  "[[]]",
172  R"cpp(int main() {
173 )cpp",
174  },
175  {
176  R"cpp(int main() {
177 )cpp",
178  ""
179  }
180  };
181  // clang-format on
182 
183  stepByStep(Steps);
184  allAtOnce(Steps);
185 }
186 
187 TEST(DraftStoreIncrementalUpdateTest, WrongRangeLength) {
188  DraftStore DS;
189  Path File = "foo.cpp";
190 
191  DS.addDraft(File, llvm::None, "int main() {}\n");
192 
193  TextDocumentContentChangeEvent Change;
194  Change.range.emplace();
195  Change.range->start.line = 0;
196  Change.range->start.character = 0;
197  Change.range->end.line = 0;
198  Change.range->end.character = 2;
199  Change.rangeLength = 10;
200 
201  Expected<DraftStore::Draft> Result =
202  DS.updateDraft(File, llvm::None, {Change});
203 
204  EXPECT_TRUE(!Result);
205  EXPECT_EQ(
206  toString(Result.takeError()),
207  "Change's rangeLength (10) doesn't match the computed range length (2).");
208 }
209 
210 TEST(DraftStoreIncrementalUpdateTest, EndBeforeStart) {
211  DraftStore DS;
212  Path File = "foo.cpp";
213 
214  DS.addDraft(File, llvm::None, "int main() {}\n");
215 
216  TextDocumentContentChangeEvent Change;
217  Change.range.emplace();
218  Change.range->start.line = 0;
219  Change.range->start.character = 5;
220  Change.range->end.line = 0;
221  Change.range->end.character = 3;
222 
223  auto Result = DS.updateDraft(File, llvm::None, {Change});
224 
225  EXPECT_TRUE(!Result);
226  EXPECT_EQ(toString(Result.takeError()),
227  "Range's end position (0:3) is before start position (0:5)");
228 }
229 
230 TEST(DraftStoreIncrementalUpdateTest, StartCharOutOfRange) {
231  DraftStore DS;
232  Path File = "foo.cpp";
233 
234  DS.addDraft(File, llvm::None, "int main() {}\n");
235 
236  TextDocumentContentChangeEvent Change;
237  Change.range.emplace();
238  Change.range->start.line = 0;
239  Change.range->start.character = 100;
240  Change.range->end.line = 0;
241  Change.range->end.character = 100;
242  Change.text = "foo";
243 
244  auto Result = DS.updateDraft(File, llvm::None, {Change});
245 
246  EXPECT_TRUE(!Result);
247  EXPECT_EQ(toString(Result.takeError()),
248  "utf-16 offset 100 is invalid for line 0");
249 }
250 
251 TEST(DraftStoreIncrementalUpdateTest, EndCharOutOfRange) {
252  DraftStore DS;
253  Path File = "foo.cpp";
254 
255  DS.addDraft(File, llvm::None, "int main() {}\n");
256 
257  TextDocumentContentChangeEvent Change;
258  Change.range.emplace();
259  Change.range->start.line = 0;
260  Change.range->start.character = 0;
261  Change.range->end.line = 0;
262  Change.range->end.character = 100;
263  Change.text = "foo";
264 
265  auto Result = DS.updateDraft(File, llvm::None, {Change});
266 
267  EXPECT_TRUE(!Result);
268  EXPECT_EQ(toString(Result.takeError()),
269  "utf-16 offset 100 is invalid for line 0");
270 }
271 
272 TEST(DraftStoreIncrementalUpdateTest, StartLineOutOfRange) {
273  DraftStore DS;
274  Path File = "foo.cpp";
275 
276  DS.addDraft(File, llvm::None, "int main() {}\n");
277 
278  TextDocumentContentChangeEvent Change;
279  Change.range.emplace();
280  Change.range->start.line = 100;
281  Change.range->start.character = 0;
282  Change.range->end.line = 100;
283  Change.range->end.character = 0;
284  Change.text = "foo";
285 
286  auto Result = DS.updateDraft(File, llvm::None, {Change});
287 
288  EXPECT_TRUE(!Result);
289  EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)");
290 }
291 
292 TEST(DraftStoreIncrementalUpdateTest, EndLineOutOfRange) {
293  DraftStore DS;
294  Path File = "foo.cpp";
295 
296  DS.addDraft(File, llvm::None, "int main() {}\n");
297 
298  TextDocumentContentChangeEvent Change;
299  Change.range.emplace();
300  Change.range->start.line = 0;
301  Change.range->start.character = 0;
302  Change.range->end.line = 100;
303  Change.range->end.character = 0;
304  Change.text = "foo";
305 
306  auto Result = DS.updateDraft(File, llvm::None, {Change});
307 
308  EXPECT_TRUE(!Result);
309  EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)");
310 }
311 
312 /// Check that if a valid change is followed by an invalid change, the original
313 /// version of the document (prior to all changes) is kept.
314 TEST(DraftStoreIncrementalUpdateTest, InvalidRangeInASequence) {
315  DraftStore DS;
316  Path File = "foo.cpp";
317 
318  StringRef OriginalContents = "int main() {}\n";
319  EXPECT_EQ(0, DS.addDraft(File, llvm::None, OriginalContents));
320 
321  // The valid change
322  TextDocumentContentChangeEvent Change1;
323  Change1.range.emplace();
324  Change1.range->start.line = 0;
325  Change1.range->start.character = 0;
326  Change1.range->end.line = 0;
327  Change1.range->end.character = 0;
328  Change1.text = "Hello ";
329 
330  // The invalid change
331  TextDocumentContentChangeEvent Change2;
332  Change2.range.emplace();
333  Change2.range->start.line = 0;
334  Change2.range->start.character = 5;
335  Change2.range->end.line = 0;
336  Change2.range->end.character = 100;
337  Change2.text = "something";
338 
339  auto Result = DS.updateDraft(File, llvm::None, {Change1, Change2});
340 
341  EXPECT_TRUE(!Result);
342  EXPECT_EQ(toString(Result.takeError()),
343  "utf-16 offset 100 is invalid for line 0");
344 
345  Optional<DraftStore::Draft> Contents = DS.getDraft(File);
346  EXPECT_TRUE(Contents);
347  EXPECT_EQ(Contents->Contents, OriginalContents);
348  EXPECT_EQ(Contents->Version, 0);
349 }
350 
351 TEST(DraftStore, Version) {
352  DraftStore DS;
353  Path File = "foo.cpp";
354 
355  EXPECT_EQ(25, DS.addDraft(File, 25, ""));
356  EXPECT_EQ(25, DS.getDraft(File)->Version);
357 
358  EXPECT_EQ(26, DS.addDraft(File, llvm::None, ""));
359  EXPECT_EQ(26, DS.getDraft(File)->Version);
360 
361  // We allow versions to go backwards.
362  EXPECT_EQ(7, DS.addDraft(File, 7, ""));
363  EXPECT_EQ(7, DS.getDraft(File)->Version);
364 
365  // Valid (no-op) change modifies version.
366  auto Result = DS.updateDraft(File, 10, {});
367  EXPECT_TRUE(!!Result);
368  EXPECT_EQ(10, Result->Version);
369  EXPECT_EQ(10, DS.getDraft(File)->Version);
370 
371  Result = DS.updateDraft(File, llvm::None, {});
372  EXPECT_TRUE(!!Result);
373  EXPECT_EQ(11, Result->Version);
374  EXPECT_EQ(11, DS.getDraft(File)->Version);
375 
376  TextDocumentContentChangeEvent InvalidChange;
377  InvalidChange.range.emplace();
378  InvalidChange.rangeLength = 99;
379 
380  Result = DS.updateDraft(File, 15, {InvalidChange});
381  EXPECT_FALSE(!!Result);
382  consumeError(Result.takeError());
383  EXPECT_EQ(11, DS.getDraft(File)->Version);
384 }
385 
386 } // namespace
387 } // namespace clangd
388 } // namespace clang
389 
std::string Code
tooling::Replacements Changes
Definition: Format.cpp:109
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Documents should not be synced at all.
llvm::StringRef Src
llvm::StringRef Contents
TEST(BackgroundQueueTest, Priority)
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:175
static const char * toString(OffsetEncoding OE)
Definition: Protocol.cpp:1170
std::string Path
A typedef to represent a file path.
Definition: Path.h:20
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
CharSourceRange Range
SourceRange for the file name.