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