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