clang-tools  10.0.0svn
TUSchedulerTests.cpp
Go to the documentation of this file.
1 //===-- TUSchedulerTests.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 "Context.h"
11 #include "Diagnostics.h"
12 #include "Matchers.h"
13 #include "ParsedAST.h"
14 #include "Path.h"
15 #include "Preamble.h"
16 #include "TUScheduler.h"
17 #include "TestFS.h"
18 #include "Threading.h"
19 #include "clang/Basic/DiagnosticDriver.h"
20 #include "llvm/ADT/ArrayRef.h"
21 #include "llvm/ADT/STLExtras.h"
22 #include "llvm/ADT/ScopeExit.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include <algorithm>
26 #include <utility>
27 
28 namespace clang {
29 namespace clangd {
30 namespace {
31 
32 using ::testing::AnyOf;
33 using ::testing::Each;
34 using ::testing::ElementsAre;
35 using ::testing::Eq;
36 using ::testing::Field;
37 using ::testing::IsEmpty;
38 using ::testing::Pointee;
39 using ::testing::UnorderedElementsAre;
40 
41 MATCHER_P2(TUState, State, ActionName, "") {
42  return arg.Action.S == State && arg.Action.Name == ActionName;
43 }
44 
45 class TUSchedulerTests : public ::testing::Test {
46 protected:
47  ParseInputs getInputs(PathRef File, std::string Contents) {
48  ParseInputs Inputs;
49  Inputs.CompileCommand = *CDB.getCompileCommand(File);
50  Inputs.FS = buildTestFS(Files, Timestamps);
51  Inputs.Contents = std::move(Contents);
52  Inputs.Opts = ParseOptions();
53  return Inputs;
54  }
55 
56  void updateWithCallback(TUScheduler &S, PathRef File,
57  llvm::StringRef Contents, WantDiagnostics WD,
58  llvm::unique_function<void()> CB) {
59  WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
60  S.update(File, getInputs(File, Contents), WD);
61  }
62 
63  static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
65 
66  /// A diagnostics callback that should be passed to TUScheduler when it's used
67  /// in updateWithDiags.
68  static std::unique_ptr<ParsingCallbacks> captureDiags() {
69  class CaptureDiags : public ParsingCallbacks {
70  public:
71  void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
72  reportDiagnostics(File, AST.getDiagnostics(), Publish);
73  }
74 
75  void onFailedAST(PathRef File, std::vector<Diag> Diags,
76  PublishFn Publish) override {
77  reportDiagnostics(File, Diags, Publish);
78  }
79 
80  private:
81  void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
82  PublishFn Publish) {
84  if (!D)
85  return;
86  Publish([&]() {
87  const_cast<
88  llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
89  File, std::move(Diags));
90  });
91  }
92  };
93  return std::make_unique<CaptureDiags>();
94  }
95 
96  /// Schedule an update and call \p CB with the diagnostics it produces, if
97  /// any. The TUScheduler should be created with captureDiags as a
98  /// DiagsCallback for this to work.
99  void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
100  WantDiagnostics WD,
101  llvm::unique_function<void(std::vector<Diag>)> CB) {
102  Path OrigFile = File.str();
103  WithContextValue Ctx(DiagsCallbackKey,
104  [OrigFile, CB = std::move(CB)](
105  PathRef File, std::vector<Diag> Diags) mutable {
106  assert(File == OrigFile);
107  CB(std::move(Diags));
108  });
109  S.update(File, std::move(Inputs), WD);
110  }
111 
112  void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
113  WantDiagnostics WD,
114  llvm::unique_function<void(std::vector<Diag>)> CB) {
115  return updateWithDiags(S, File, getInputs(File, Contents), WD,
116  std::move(CB));
117  }
118 
119  llvm::StringMap<std::string> Files;
120  llvm::StringMap<time_t> Timestamps;
121  MockCompilationDatabase CDB;
122 };
123 
124 Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
126 
127 TEST_F(TUSchedulerTests, MissingFiles) {
128  TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
129  /*StorePreamblesInMemory=*/true, /*ASTCallbacks=*/nullptr,
130  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
131  ASTRetentionPolicy());
132 
133  auto Added = testPath("added.cpp");
134  Files[Added] = "x";
135 
136  auto Missing = testPath("missing.cpp");
137  Files[Missing] = "";
138 
139  EXPECT_EQ(S.getContents(Added), "");
140  S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
141  EXPECT_EQ(S.getContents(Added), "x");
142 
143  // Assert each operation for missing file is an error (even if it's
144  // available in VFS).
145  S.runWithAST("", Missing,
146  [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
147  S.runWithPreamble(
149  [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
150  // remove() shouldn't crash on missing files.
151  S.remove(Missing);
152 
153  // Assert there aren't any errors for added file.
154  S.runWithAST("", Added,
155  [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
156  S.runWithPreamble("", Added, TUScheduler::Stale,
157  [&](Expected<InputsAndPreamble> Preamble) {
158  EXPECT_TRUE(bool(Preamble));
159  });
160  EXPECT_EQ(S.getContents(Added), "x");
161  S.remove(Added);
162  EXPECT_EQ(S.getContents(Added), "");
163 
164  // Assert that all operations fail after removing the file.
165  S.runWithAST("", Added,
166  [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
167  S.runWithPreamble("", Added, TUScheduler::Stale,
168  [&](Expected<InputsAndPreamble> Preamble) {
169  ASSERT_FALSE(bool(Preamble));
170  llvm::consumeError(Preamble.takeError());
171  });
172  // remove() shouldn't crash on missing files.
173  S.remove(Added);
174 }
175 
176 TEST_F(TUSchedulerTests, WantDiagnostics) {
177  std::atomic<int> CallbackCount(0);
178  {
179  // To avoid a racy test, don't allow tasks to actualy run on the worker
180  // thread until we've scheduled them all.
181  Notification Ready;
182  TUScheduler S(
184  /*StorePreamblesInMemory=*/true, captureDiags(),
185  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
186  ASTRetentionPolicy());
187  auto Path = testPath("foo.cpp");
188  updateWithDiags(S, Path, "", WantDiagnostics::Yes,
189  [&](std::vector<Diag>) { Ready.wait(); });
190  updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes,
191  [&](std::vector<Diag>) { ++CallbackCount; });
192  updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto,
193  [&](std::vector<Diag>) {
194  ADD_FAILURE()
195  << "auto should have been cancelled by auto";
196  });
197  updateWithDiags(S, Path, "request no diags", WantDiagnostics::No,
198  [&](std::vector<Diag>) {
199  ADD_FAILURE() << "no diags should not be called back";
200  });
201  updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto,
202  [&](std::vector<Diag>) { ++CallbackCount; });
203  Ready.notify();
204 
205  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
206  }
207  EXPECT_EQ(2, CallbackCount);
208 }
209 
210 TEST_F(TUSchedulerTests, Debounce) {
211  std::atomic<int> CallbackCount(0);
212  {
213  TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
214  /*StorePreamblesInMemory=*/true, captureDiags(),
215  /*UpdateDebounce=*/std::chrono::seconds(1),
216  ASTRetentionPolicy());
217  // FIXME: we could probably use timeouts lower than 1 second here.
218  auto Path = testPath("foo.cpp");
219  updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
220  [&](std::vector<Diag>) {
221  ADD_FAILURE()
222  << "auto should have been debounced and canceled";
223  });
224  std::this_thread::sleep_for(std::chrono::milliseconds(200));
225  updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
226  [&](std::vector<Diag>) { ++CallbackCount; });
227  std::this_thread::sleep_for(std::chrono::seconds(2));
228  updateWithDiags(S, Path, "auto (shut down)", WantDiagnostics::Auto,
229  [&](std::vector<Diag>) { ++CallbackCount; });
230 
231  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
232  }
233  EXPECT_EQ(2, CallbackCount);
234 }
235 
236 static std::vector<std::string> includes(const PreambleData *Preamble) {
237  std::vector<std::string> Result;
238  if (Preamble)
239  for (const auto &Inclusion : Preamble->Includes.MainFileIncludes)
240  Result.push_back(Inclusion.Written);
241  return Result;
242 }
243 
244 TEST_F(TUSchedulerTests, PreambleConsistency) {
245  std::atomic<int> CallbackCount(0);
246  {
247  Notification InconsistentReadDone; // Must live longest.
248  TUScheduler S(
249  CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
250  /*ASTCallbacks=*/nullptr,
251  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
252  ASTRetentionPolicy());
253  auto Path = testPath("foo.cpp");
254  // Schedule two updates (A, B) and two preamble reads (stale, consistent).
255  // The stale read should see A, and the consistent read should see B.
256  // (We recognize the preambles by their included files).
257  updateWithCallback(S, Path, "#include <A>", WantDiagnostics::Yes, [&]() {
258  // This callback runs in between the two preamble updates.
259 
260  // This blocks update B, preventing it from winning the race
261  // against the stale read.
262  // If the first read was instead consistent, this would deadlock.
263  InconsistentReadDone.wait();
264  // This delays update B, preventing it from winning a race
265  // against the consistent read. The consistent read sees B
266  // only because it waits for it.
267  // If the second read was stale, it would usually see A.
268  std::this_thread::sleep_for(std::chrono::milliseconds(100));
269  });
270  S.update(Path, getInputs(Path, "#include <B>"), WantDiagnostics::Yes);
271 
272  S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
273  [&](Expected<InputsAndPreamble> Pre) {
274  ASSERT_TRUE(bool(Pre));
275  assert(bool(Pre));
276  EXPECT_THAT(includes(Pre->Preamble),
277  ElementsAre("<A>"));
278  InconsistentReadDone.notify();
279  ++CallbackCount;
280  });
281  S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
282  [&](Expected<InputsAndPreamble> Pre) {
283  ASSERT_TRUE(bool(Pre));
284  EXPECT_THAT(includes(Pre->Preamble),
285  ElementsAre("<B>"));
286  ++CallbackCount;
287  });
288  }
289  EXPECT_EQ(2, CallbackCount);
290 }
291 
292 TEST_F(TUSchedulerTests, Cancellation) {
293  // We have the following update/read sequence
294  // U0
295  // U1(WantDiags=Yes) <-- cancelled
296  // R1 <-- cancelled
297  // U2(WantDiags=Yes) <-- cancelled
298  // R2A <-- cancelled
299  // R2B
300  // U3(WantDiags=Yes)
301  // R3 <-- cancelled
302  std::vector<std::string> DiagsSeen, ReadsSeen, ReadsCanceled;
303  {
304  Notification Proceed; // Ensure we schedule everything.
305  TUScheduler S(
306  CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
307  /*ASTCallbacks=*/captureDiags(),
308  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
309  ASTRetentionPolicy());
310  auto Path = testPath("foo.cpp");
311  // Helper to schedule a named update and return a function to cancel it.
312  auto Update = [&](std::string ID) -> Canceler {
313  auto T = cancelableTask();
314  WithContext C(std::move(T.first));
315  updateWithDiags(
316  S, Path, "//" + ID, WantDiagnostics::Yes,
317  [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
318  return std::move(T.second);
319  };
320  // Helper to schedule a named read and return a function to cancel it.
321  auto Read = [&](std::string ID) -> Canceler {
322  auto T = cancelableTask();
323  WithContext C(std::move(T.first));
324  S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
325  if (auto Err = E.takeError()) {
326  if (Err.isA<CancelledError>()) {
327  ReadsCanceled.push_back(ID);
328  consumeError(std::move(Err));
329  } else {
330  ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
331  << llvm::toString(std::move(Err));
332  }
333  } else {
334  ReadsSeen.push_back(ID);
335  }
336  });
337  return std::move(T.second);
338  };
339 
340  updateWithCallback(S, Path, "", WantDiagnostics::Yes,
341  [&]() { Proceed.wait(); });
342  // The second parens indicate cancellation, where present.
343  Update("U1")();
344  Read("R1")();
345  Update("U2")();
346  Read("R2A")();
347  Read("R2B");
348  Update("U3");
349  Read("R3")();
350  Proceed.notify();
351 
352  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
353  }
354  EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
355  << "U1 and all dependent reads were cancelled. "
356  "U2 has a dependent read R2A. "
357  "U3 was not cancelled.";
358  EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
359  << "All reads other than R2B were cancelled";
360  EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
361  << "All reads other than R2B were cancelled";
362 }
363 
364 TEST_F(TUSchedulerTests, ManyUpdates) {
365  const int FilesCount = 3;
366  const int UpdatesPerFile = 10;
367 
368  std::mutex Mut;
369  int TotalASTReads = 0;
370  int TotalPreambleReads = 0;
371  int TotalUpdates = 0;
372 
373  // Run TUScheduler and collect some stats.
374  {
375  TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
376  /*StorePreamblesInMemory=*/true, captureDiags(),
377  /*UpdateDebounce=*/std::chrono::milliseconds(50),
378  ASTRetentionPolicy());
379 
380  std::vector<std::string> Files;
381  for (int I = 0; I < FilesCount; ++I) {
382  std::string Name = "foo" + std::to_string(I) + ".cpp";
383  Files.push_back(testPath(Name));
384  this->Files[Files.back()] = "";
385  }
386 
387  StringRef Contents1 = R"cpp(int a;)cpp";
388  StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
389  StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
390 
391  StringRef AllContents[] = {Contents1, Contents2, Contents3};
392  const int AllContentsSize = 3;
393 
394  // Scheduler may run tasks asynchronously, but should propagate the
395  // context. We stash a nonce in the context, and verify it in the task.
396  static Key<int> NonceKey;
397  int Nonce = 0;
398 
399  for (int FileI = 0; FileI < FilesCount; ++FileI) {
400  for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
401  auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
402 
403  auto File = Files[FileI];
404  auto Inputs = getInputs(File, Contents.str());
405  {
406  WithContextValue WithNonce(NonceKey, ++Nonce);
407  updateWithDiags(
408  S, File, Inputs, WantDiagnostics::Auto,
409  [File, Nonce, &Mut, &TotalUpdates](std::vector<Diag>) {
410  EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
411 
412  std::lock_guard<std::mutex> Lock(Mut);
413  ++TotalUpdates;
415  });
416  }
417  {
418  WithContextValue WithNonce(NonceKey, ++Nonce);
419  S.runWithAST(
420  "CheckAST", File,
421  [File, Inputs, Nonce, &Mut,
422  &TotalASTReads](Expected<InputsAndAST> AST) {
423  EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
424 
425  ASSERT_TRUE((bool)AST);
426  EXPECT_EQ(AST->Inputs.FS, Inputs.FS);
427  EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
428 
429  std::lock_guard<std::mutex> Lock(Mut);
430  ++TotalASTReads;
432  });
433  }
434 
435  {
436  WithContextValue WithNonce(NonceKey, ++Nonce);
437  S.runWithPreamble(
438  "CheckPreamble", File, TUScheduler::Stale,
439  [File, Inputs, Nonce, &Mut,
440  &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
441  EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
442 
443  ASSERT_TRUE((bool)Preamble);
444  EXPECT_EQ(Preamble->Contents, Inputs.Contents);
445 
446  std::lock_guard<std::mutex> Lock(Mut);
447  ++TotalPreambleReads;
449  });
450  }
451  }
452  }
453  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
454  } // TUScheduler destructor waits for all operations to finish.
455 
456  std::lock_guard<std::mutex> Lock(Mut);
457  EXPECT_EQ(TotalUpdates, FilesCount * UpdatesPerFile);
458  EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
459  EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
460 }
461 
462 TEST_F(TUSchedulerTests, EvictedAST) {
463  std::atomic<int> BuiltASTCounter(0);
464  ASTRetentionPolicy Policy;
465  Policy.MaxRetainedASTs = 2;
466  TUScheduler S(CDB,
467  /*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true,
468  /*ASTCallbacks=*/nullptr,
469  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
470  Policy);
471 
472  llvm::StringLiteral SourceContents = R"cpp(
473  int* a;
474  double* b = a;
475  )cpp";
476  llvm::StringLiteral OtherSourceContents = R"cpp(
477  int* a;
478  double* b = a + 0;
479  )cpp";
480 
481  auto Foo = testPath("foo.cpp");
482  auto Bar = testPath("bar.cpp");
483  auto Baz = testPath("baz.cpp");
484 
485  // Build one file in advance. We will not access it later, so it will be the
486  // one that the cache will evict.
487  updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
488  [&BuiltASTCounter]() { ++BuiltASTCounter; });
489  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
490  ASSERT_EQ(BuiltASTCounter.load(), 1);
491 
492  // Build two more files. Since we can retain only 2 ASTs, these should be
493  // the ones we see in the cache later.
494  updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
495  [&BuiltASTCounter]() { ++BuiltASTCounter; });
496  updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
497  [&BuiltASTCounter]() { ++BuiltASTCounter; });
498  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
499  ASSERT_EQ(BuiltASTCounter.load(), 3);
500 
501  // Check only the last two ASTs are retained.
502  ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
503 
504  // Access the old file again.
505  updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
506  [&BuiltASTCounter]() { ++BuiltASTCounter; });
507  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
508  ASSERT_EQ(BuiltASTCounter.load(), 4);
509 
510  // Check the AST for foo.cpp is retained now and one of the others got
511  // evicted.
512  EXPECT_THAT(S.getFilesWithCachedAST(),
513  UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
514 }
515 
516 TEST_F(TUSchedulerTests, EmptyPreamble) {
517  TUScheduler S(CDB,
518  /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
519  /*ASTCallbacks=*/nullptr,
520  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
521  ASTRetentionPolicy());
522 
523  auto Foo = testPath("foo.cpp");
524  auto Header = testPath("foo.h");
525 
526  Files[Header] = "void foo()";
527  Timestamps[Header] = time_t(0);
528  auto WithPreamble = R"cpp(
529  #include "foo.h"
530  int main() {}
531  )cpp";
532  auto WithEmptyPreamble = R"cpp(int main() {})cpp";
533  S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
534  S.runWithPreamble(
535  "getNonEmptyPreamble", Foo, TUScheduler::Stale,
536  [&](Expected<InputsAndPreamble> Preamble) {
537  // We expect to get a non-empty preamble.
538  EXPECT_GT(
539  cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
540  0u);
541  });
542  // Wait for the preamble is being built.
543  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
544 
545  // Update the file which results in an empty preamble.
546  S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
547  // Wait for the preamble is being built.
548  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
549  S.runWithPreamble(
550  "getEmptyPreamble", Foo, TUScheduler::Stale,
551  [&](Expected<InputsAndPreamble> Preamble) {
552  // We expect to get an empty preamble.
553  EXPECT_EQ(
554  cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
555  0u);
556  });
557 }
558 
559 TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
560  // Testing strategy: we update the file and schedule a few preamble reads at
561  // the same time. All reads should get the same non-null preamble.
562  TUScheduler S(CDB,
563  /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
564  /*ASTCallbacks=*/nullptr,
565  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
566  ASTRetentionPolicy());
567  auto Foo = testPath("foo.cpp");
568  auto NonEmptyPreamble = R"cpp(
569  #define FOO 1
570  #define BAR 2
571 
572  int main() {}
573  )cpp";
574  constexpr int ReadsToSchedule = 10;
575  std::mutex PreamblesMut;
576  std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
577  S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
578  for (int I = 0; I < ReadsToSchedule; ++I) {
579  S.runWithPreamble(
580  "test", Foo, TUScheduler::Stale,
581  [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
582  std::lock_guard<std::mutex> Lock(PreamblesMut);
583  Preambles[I] = cantFail(std::move(IP)).Preamble;
584  });
585  }
586  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
587  // Check all actions got the same non-null preamble.
588  std::lock_guard<std::mutex> Lock(PreamblesMut);
589  ASSERT_NE(Preambles[0], nullptr);
590  ASSERT_THAT(Preambles, Each(Preambles[0]));
591 }
592 
593 TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
594  TUScheduler S(CDB,
595  /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
596  /*StorePreambleInMemory=*/true, captureDiags(),
597  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
598  ASTRetentionPolicy());
599 
600  auto Source = testPath("foo.cpp");
601  auto Header = testPath("foo.h");
602 
603  Files[Header] = "int a;";
604  Timestamps[Header] = time_t(0);
605 
606  auto SourceContents = R"cpp(
607  #include "foo.h"
608  int b = a;
609  )cpp";
610 
611  // Return value indicates if the updated callback was received.
612  auto DoUpdate = [&](std::string Contents) -> bool {
613  std::atomic<bool> Updated(false);
614  Updated = false;
615  updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
616  [&Updated](std::vector<Diag>) { Updated = true; });
617  bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10));
618  if (!UpdateFinished)
619  ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
620  return Updated;
621  };
622 
623  // Test that subsequent updates with the same inputs do not cause rebuilds.
624  ASSERT_TRUE(DoUpdate(SourceContents));
625  ASSERT_FALSE(DoUpdate(SourceContents));
626 
627  // Update to a header should cause a rebuild, though.
628  Timestamps[Header] = time_t(1);
629  ASSERT_TRUE(DoUpdate(SourceContents));
630  ASSERT_FALSE(DoUpdate(SourceContents));
631 
632  // Update to the contents should cause a rebuild.
633  auto OtherSourceContents = R"cpp(
634  #include "foo.h"
635  int c = d;
636  )cpp";
637  ASSERT_TRUE(DoUpdate(OtherSourceContents));
638  ASSERT_FALSE(DoUpdate(OtherSourceContents));
639 
640  // Update to the compile commands should also cause a rebuild.
641  CDB.ExtraClangFlags.push_back("-DSOMETHING");
642  ASSERT_TRUE(DoUpdate(OtherSourceContents));
643  ASSERT_FALSE(DoUpdate(OtherSourceContents));
644 }
645 
646 TEST_F(TUSchedulerTests, NoChangeDiags) {
647  TUScheduler S(CDB,
648  /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
649  /*StorePreambleInMemory=*/true, captureDiags(),
650  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
651  ASTRetentionPolicy());
652 
653  auto FooCpp = testPath("foo.cpp");
654  auto Contents = "int a; int b;";
655 
656  updateWithDiags(
657  S, FooCpp, Contents, WantDiagnostics::No,
658  [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
659  S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
660  // Make sure the AST was actually built.
661  cantFail(std::move(IA));
662  });
663  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
664 
665  // Even though the inputs didn't change and AST can be reused, we need to
666  // report the diagnostics, as they were not reported previously.
667  std::atomic<bool> SeenDiags(false);
668  updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
669  [&](std::vector<Diag>) { SeenDiags = true; });
670  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
671  ASSERT_TRUE(SeenDiags);
672 
673  // Subsequent request does not get any diagnostics callback because the same
674  // diags have previously been reported and the inputs didn't change.
675  updateWithDiags(
676  S, FooCpp, Contents, WantDiagnostics::Auto,
677  [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
678  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
679 }
680 
681 TEST_F(TUSchedulerTests, Run) {
682  TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
683  /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/nullptr,
684  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
685  ASTRetentionPolicy());
686  std::atomic<int> Counter(0);
687  S.run("add 1", [&] { ++Counter; });
688  S.run("add 2", [&] { Counter += 2; });
689  ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
690  EXPECT_EQ(Counter.load(), 3);
691 }
692 
693 TEST_F(TUSchedulerTests, TUStatus) {
694  class CaptureTUStatus : public DiagnosticsConsumer {
695  public:
696  void onDiagnosticsReady(PathRef File,
697  std::vector<Diag> Diagnostics) override {}
698 
699  void onFileUpdated(PathRef File, const TUStatus &Status) override {
700  std::lock_guard<std::mutex> Lock(Mutex);
701  AllStatus.push_back(Status);
702  }
703 
704  std::vector<TUStatus> allStatus() {
705  std::lock_guard<std::mutex> Lock(Mutex);
706  return AllStatus;
707  }
708 
709  private:
710  std::mutex Mutex;
711  std::vector<TUStatus> AllStatus;
712  } CaptureTUStatus;
713  MockFSProvider FS;
714  MockCompilationDatabase CDB;
715  ClangdServer Server(CDB, FS, CaptureTUStatus, ClangdServer::optsForTest());
716  Annotations Code("int m^ain () {}");
717 
718  // We schedule the following tasks in the queue:
719  // [Update] [GoToDefinition]
720  Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes);
721  Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
722  [](Expected<std::vector<LocatedSymbol>> Result) {
723  ASSERT_TRUE((bool)Result);
724  });
725 
726  ASSERT_TRUE(Server.blockUntilIdleForTest());
727 
728  EXPECT_THAT(CaptureTUStatus.allStatus(),
729  ElementsAre(
730  // Statuses of "Update" action.
731  TUState(TUAction::RunningAction, "Update"),
732  TUState(TUAction::BuildingPreamble, "Update"),
733  TUState(TUAction::BuildingFile, "Update"),
734 
735  // Statuses of "Definitions" action
736  TUState(TUAction::RunningAction, "Definitions"),
737  TUState(TUAction::Idle, /*No action*/ "")));
738 }
739 
740 TEST_F(TUSchedulerTests, CommandLineErrors) {
741  // We should see errors from command-line parsing inside the main file.
742  CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
743 
744  // (!) 'Ready' must live longer than TUScheduler.
745  Notification Ready;
746 
747  TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
748  /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
749  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
750  ASTRetentionPolicy());
751 
752  std::vector<Diag> Diagnostics;
753  updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
754  WantDiagnostics::Yes, [&](std::vector<Diag> D) {
755  Diagnostics = std::move(D);
756  Ready.notify();
757  });
758  Ready.wait();
759 
760  EXPECT_THAT(
761  Diagnostics,
762  ElementsAre(AllOf(
763  Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
764  Field(&Diag::Name, Eq("drv_unknown_argument")),
765  Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
766 }
767 
768 TEST_F(TUSchedulerTests, CommandLineWarnings) {
769  // We should not see warnings from command-line parsing.
770  CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
771 
772  // (!) 'Ready' must live longer than TUScheduler.
773  Notification Ready;
774 
775  TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
776  /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
777  /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
778  ASTRetentionPolicy());
779 
780  std::vector<Diag> Diagnostics;
781  updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
782  WantDiagnostics::Yes, [&](std::vector<Diag> D) {
783  Diagnostics = std::move(D);
784  Ready.notify();
785  });
786  Ready.wait();
787 
788  EXPECT_THAT(Diagnostics, IsEmpty());
789 }
790 
791 } // namespace
792 } // namespace clangd
793 } // namespace clang
std::string Code
WantDiagnostics
Determines whether diagnostics should be generated for a file snapshot.
Definition: TUScheduler.h:47
llvm::StringRef Contents
MockCompilationDatabase CDB
The preamble may be generated from an older version of the file.
Definition: TUScheduler.h:211
Diagnostics must be generated for this snapshot.
std::function< void()> Canceler
A canceller requests cancellation of a task, when called.
Definition: Cancellation.h:70
static llvm::Optional< llvm::StringRef > getFileBeingProcessedInContext()
Definition: TUScheduler.cpp:76
#define EXPECT_ERROR(expectedValue)
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:23
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
MockFSProvider FS
static Options optsForTest()
std::string Name
Definition: Diagnostics.h:86
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
const Type * get(const Key< Type > &Key) const
Get data stored for a typed Key.
Definition: Context.h:100
Context Ctx
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > buildTestFS(llvm::StringMap< std::string > const &Files, llvm::StringMap< time_t > const &Timestamps)
Definition: TestFS.cpp:22
std::string testPath(PathRef File)
Definition: TestFS.cpp:82
std::string Path
A typedef to represent a file path.
Definition: Path.h:20
static const Context & current()
Returns the context for the current thread, creating it if needed.
Definition: Context.cpp:27
static constexpr llvm::StringLiteral Name
const Decl * D
Definition: XRefs.cpp:849
std::pair< Context, Canceler > cancelableTask()
Defines a new task whose cancellation may be requested.
unsigned getDefaultAsyncThreadsCount()
Returns a number of a default async threads to use for TUScheduler.
llvm::StringMap< time_t > Timestamps
const PreambleData * Preamble
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static Key< llvm::unique_function< void(PathRef File, std::vector< Diag >)> > DiagsCallbackKey
Deadline timeoutSeconds(llvm::Optional< double > Seconds)
Makes a deadline from a timeout in seconds. None means wait forever.
Definition: Threading.cpp:99
ClangdServer Server
Diagnostics must not be generated for this snapshot.
llvm::StringMap< std::string > Files
The preamble is generated from the current version of the file.
Definition: TUScheduler.h:203