//===-- 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 #include 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, 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 Index = {0}; std::vector 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 &Argv) { Argv.push_back(Arg); }); return true; }; return {F}; } public: FakeProvider(llvm::StringRef Prefix) : Prefix(Prefix) {} }; std::vector getAddedArgs(Config &C) { std::vector 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 *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"] = 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