| //===-- ConfigProviderTests.cpp -------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Config.h" |
| #include "ConfigProvider.h" |
| #include "ConfigTesting.h" |
| #include "TestFS.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/SourceMgr.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <atomic> |
| #include <chrono> |
| |
| namespace clang { |
| namespace clangd { |
| namespace config { |
| namespace { |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| |
| // Provider that appends an arg to compile flags. |
| // The arg is prefix<N>, where N is the times getFragments() was called. |
| // It also yields a diagnostic each time it's called. |
| class FakeProvider : public Provider { |
| std::string Prefix; |
| mutable std::atomic<unsigned> Index = {0}; |
| |
| std::vector<CompiledFragment> |
| getFragments(const Params &, DiagnosticCallback DC) const override { |
| DC(llvm::SMDiagnostic("", llvm::SourceMgr::DK_Error, Prefix)); |
| CompiledFragment F = |
| [Arg(Prefix + std::to_string(++Index))](const Params &P, Config &C) { |
| C.CompileFlags.Edits.push_back( |
| [Arg](std::vector<std::string> &Argv) { Argv.push_back(Arg); }); |
| return true; |
| }; |
| return {F}; |
| } |
| |
| public: |
| FakeProvider(llvm::StringRef Prefix) : Prefix(Prefix) {} |
| }; |
| |
| std::vector<std::string> getAddedArgs(Config &C) { |
| std::vector<std::string> Argv; |
| for (auto &Edit : C.CompileFlags.Edits) |
| Edit(Argv); |
| return Argv; |
| } |
| |
| // The provider from combine() should invoke its providers in order, and not |
| // cache their results. |
| TEST(ProviderTest, Combine) { |
| CapturedDiags Diags; |
| FakeProvider Foo("foo"); |
| FakeProvider Bar("bar"); |
| auto Combined = Provider::combine({&Foo, &Bar}); |
| Config Cfg = Combined->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, |
| ElementsAre(DiagMessage("foo"), DiagMessage("bar"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo1", "bar1")); |
| Diags.Diagnostics.clear(); |
| |
| Cfg = Combined->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, |
| ElementsAre(DiagMessage("foo"), DiagMessage("bar"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo2", "bar2")); |
| } |
| |
| const char *AddFooWithErr = R"yaml( |
| CompileFlags: |
| Add: foo |
| Unknown: 42 |
| )yaml"; |
| |
| const char *AddFooWithTypoErr = R"yaml( |
| CompileFlags: |
| Add: foo |
| Removr: 42 |
| )yaml"; |
| |
| const char *AddBarBaz = R"yaml( |
| CompileFlags: |
| Add: bar |
| --- |
| CompileFlags: |
| Add: baz |
| )yaml"; |
| |
| TEST(ProviderTest, FromYAMLFile) { |
| MockFS FS; |
| FS.Files["foo.yaml"] = AddFooWithErr; |
| |
| CapturedDiags Diags; |
| auto P = Provider::fromYAMLFile(testPath("foo.yaml"), /*Directory=*/"", FS); |
| auto Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, |
| ElementsAre(DiagMessage("Unknown CompileFlags key 'Unknown'"))); |
| EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo")); |
| Diags.clear(); |
| |
| Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed"; |
| EXPECT_THAT(Diags.Files, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo")); |
| |
| FS.Files["foo.yaml"] = AddFooWithTypoErr; |
| Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT( |
| Diags.Diagnostics, |
| ElementsAre(DiagMessage( |
| "Unknown CompileFlags key 'Removr'; did you mean 'Remove'?"))); |
| EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo")); |
| Diags.clear(); |
| |
| FS.Files["foo.yaml"] = AddBarBaz; |
| Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors"; |
| EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz")); |
| Diags.clear(); |
| |
| FS.Files.erase("foo.yaml"); |
| Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Missing file is not an error"; |
| EXPECT_THAT(Diags.Files, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), IsEmpty()); |
| } |
| |
| TEST(ProviderTest, FromAncestorRelativeYAMLFiles) { |
| MockFS FS; |
| FS.Files["a/b/c/foo.yaml"] = AddBarBaz; |
| FS.Files["a/foo.yaml"] = AddFooWithErr; |
| |
| std::string ABCPath = |
| testPath("a/b/c/d/test.cc", llvm::sys::path::Style::posix); |
| Params ABCParams; |
| ABCParams.Path = ABCPath; |
| std::string APath = |
| testPath("a/b/e/f/test.cc", llvm::sys::path::Style::posix); |
| Params AParams; |
| AParams.Path = APath; |
| |
| CapturedDiags Diags; |
| auto P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS); |
| |
| auto Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
| EXPECT_THAT(Diags.Files, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), IsEmpty()); |
| |
| Cfg = P->getConfig(ABCParams, Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, |
| ElementsAre(DiagMessage("Unknown CompileFlags key 'Unknown'"))); |
| // FIXME: fails on windows: paths have mixed slashes like C:\a/b\c.yaml |
| EXPECT_THAT(Diags.Files, |
| ElementsAre(testPath("a/foo.yaml"), testPath("a/b/c/foo.yaml"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo", "bar", "baz")); |
| Diags.clear(); |
| |
| Cfg = P->getConfig(AParams, Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached config"; |
| EXPECT_THAT(Diags.Files, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo")); |
| |
| FS.Files.erase("a/foo.yaml"); |
| Cfg = P->getConfig(ABCParams, Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
| EXPECT_THAT(Diags.Files, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz")); |
| } |
| |
| // FIXME: delete this test, it's covered by FileCacheTests. |
| TEST(ProviderTest, Staleness) { |
| MockFS FS; |
| |
| auto StartTime = std::chrono::steady_clock::now(); |
| Params StaleOK; |
| StaleOK.FreshTime = StartTime; |
| Params MustBeFresh; |
| MustBeFresh.FreshTime = StartTime + std::chrono::hours(1); |
| CapturedDiags Diags; |
| auto P = Provider::fromYAMLFile(testPath("foo.yaml"), /*Directory=*/"", FS); |
| |
| // Initial query always reads, regardless of policy. |
| FS.Files["foo.yaml"] = AddFooWithErr; |
| auto Cfg = P->getConfig(StaleOK, Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, |
| ElementsAre(DiagMessage("Unknown CompileFlags key 'Unknown'"))); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo")); |
| Diags.clear(); |
| |
| // Stale value reused by policy. |
| FS.Files["foo.yaml"] = AddBarBaz; |
| Cfg = P->getConfig(StaleOK, Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed"; |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo")); |
| |
| // Cache revalidated by policy. |
| Cfg = P->getConfig(MustBeFresh, Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors"; |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz")); |
| |
| // Cache revalidated by (default) policy. |
| FS.Files.erase("foo.yaml"); |
| Cfg = P->getConfig(Params(), Diags.callback()); |
| EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), IsEmpty()); |
| } |
| |
| TEST(ProviderTest, SourceInfo) { |
| MockFS FS; |
| |
| FS.Files["baz/foo.yaml"] = R"yaml( |
| If: |
| PathMatch: .* |
| PathExclude: bar.h |
| CompileFlags: |
| Add: bar |
| )yaml"; |
| const auto BarPath = testPath("baz/bar.h", llvm::sys::path::Style::posix); |
| CapturedDiags Diags; |
| Params Bar; |
| Bar.Path = BarPath; |
| |
| // This should be an absolute match/exclude hence baz/bar.h should not be |
| // excluded. |
| auto P = |
| Provider::fromYAMLFile(testPath("baz/foo.yaml"), /*Directory=*/"", FS); |
| auto Cfg = P->getConfig(Bar, Diags.callback()); |
| ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar")); |
| Diags.clear(); |
| |
| // This should be a relative match/exclude hence baz/bar.h should be excluded. |
| P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS); |
| Cfg = P->getConfig(Bar, Diags.callback()); |
| ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
| EXPECT_THAT(getAddedArgs(Cfg), IsEmpty()); |
| Diags.clear(); |
| } |
| } // namespace |
| } // namespace config |
| } // namespace clangd |
| } // namespace clang |