1 //===-- ConfigCompileTests.cpp --------------------------------------------===// 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 "Config.h" 10 #include "ConfigFragment.h" 11 #include "ConfigTesting.h" 12 #include "Feature.h" 13 #include "TestFS.h" 14 #include "clang/Basic/DiagnosticSema.h" 15 #include "llvm/ADT/None.h" 16 #include "llvm/ADT/Optional.h" 17 #include "llvm/ADT/StringRef.h" 18 #include "llvm/Support/Path.h" 19 #include "llvm/Support/SourceMgr.h" 20 #include "gmock/gmock.h" 21 #include "gtest/gtest.h" 22 #include <string> 23 24 namespace clang { 25 namespace clangd { 26 namespace config { 27 namespace { 28 using ::testing::AllOf; 29 using ::testing::Contains; 30 using ::testing::ElementsAre; 31 using ::testing::IsEmpty; 32 using ::testing::SizeIs; 33 using ::testing::StartsWith; 34 using ::testing::UnorderedElementsAre; 35 36 class ConfigCompileTests : public ::testing::Test { 37 protected: 38 CapturedDiags Diags; 39 Config Conf; 40 Fragment Frag; 41 Params Parm; 42 43 bool compileAndApply() { 44 Conf = Config(); 45 Diags.Diagnostics.clear(); 46 auto Compiled = std::move(Frag).compile(Diags.callback()); 47 return Compiled(Parm, Conf); 48 } 49 }; 50 51 TEST_F(ConfigCompileTests, Condition) { 52 // No condition. 53 Frag = {}; 54 Frag.CompileFlags.Add.emplace_back("X"); 55 EXPECT_TRUE(compileAndApply()) << "Empty config"; 56 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 57 EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(1)); 58 59 // Regex with no file. 60 Frag = {}; 61 Frag.If.PathMatch.emplace_back("fo*"); 62 EXPECT_FALSE(compileAndApply()); 63 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 64 EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(0)); 65 66 // Following tests have a file path set. 67 Parm.Path = "bar"; 68 69 // Non-matching regex. 70 Frag = {}; 71 Frag.If.PathMatch.emplace_back("fo*"); 72 EXPECT_FALSE(compileAndApply()); 73 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 74 75 // Matching regex. 76 Frag = {}; 77 Frag.If.PathMatch.emplace_back("fo*"); 78 Frag.If.PathMatch.emplace_back("ba*r"); 79 EXPECT_TRUE(compileAndApply()); 80 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 81 82 // Excluded regex. 83 Frag = {}; 84 Frag.If.PathMatch.emplace_back("b.*"); 85 Frag.If.PathExclude.emplace_back(".*r"); 86 EXPECT_FALSE(compileAndApply()) << "Included but also excluded"; 87 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 88 89 // Invalid regex. 90 Frag = {}; 91 Frag.If.PathMatch.emplace_back("**]@theu"); 92 EXPECT_TRUE(compileAndApply()); 93 EXPECT_THAT(Diags.Diagnostics, SizeIs(1)); 94 EXPECT_THAT(Diags.Diagnostics.front().Message, StartsWith("Invalid regex")); 95 96 // Valid regex and unknown key. 97 Frag = {}; 98 Frag.If.HasUnrecognizedCondition = true; 99 Frag.If.PathMatch.emplace_back("ba*r"); 100 EXPECT_FALSE(compileAndApply()); 101 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 102 103 // Only matches case-insensitively. 104 Frag = {}; 105 Frag.If.PathMatch.emplace_back("B.*R"); 106 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 107 #ifdef CLANGD_PATH_CASE_INSENSITIVE 108 EXPECT_TRUE(compileAndApply()); 109 #else 110 EXPECT_FALSE(compileAndApply()); 111 #endif 112 113 Frag = {}; 114 Frag.If.PathExclude.emplace_back("B.*R"); 115 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 116 #ifdef CLANGD_PATH_CASE_INSENSITIVE 117 EXPECT_FALSE(compileAndApply()); 118 #else 119 EXPECT_TRUE(compileAndApply()); 120 #endif 121 } 122 123 TEST_F(ConfigCompileTests, CompileCommands) { 124 Frag.CompileFlags.Add.emplace_back("-foo"); 125 Frag.CompileFlags.Remove.emplace_back("--include-directory="); 126 std::vector<std::string> Argv = {"clang", "-I", "bar/", "--", "a.cc"}; 127 EXPECT_TRUE(compileAndApply()); 128 EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(2)); 129 for (auto &Edit : Conf.CompileFlags.Edits) 130 Edit(Argv); 131 EXPECT_THAT(Argv, ElementsAre("clang", "-foo", "--", "a.cc")); 132 } 133 134 TEST_F(ConfigCompileTests, CompilationDatabase) { 135 Frag.CompileFlags.CompilationDatabase.emplace("None"); 136 EXPECT_TRUE(compileAndApply()); 137 EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, 138 Config::CDBSearchSpec::NoCDBSearch); 139 140 Frag.CompileFlags.CompilationDatabase.emplace("Ancestors"); 141 EXPECT_TRUE(compileAndApply()); 142 EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, 143 Config::CDBSearchSpec::Ancestors); 144 145 // Relative path not allowed without directory set. 146 Frag.CompileFlags.CompilationDatabase.emplace("Something"); 147 EXPECT_TRUE(compileAndApply()); 148 EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, 149 Config::CDBSearchSpec::Ancestors) 150 << "default value"; 151 EXPECT_THAT(Diags.Diagnostics, 152 ElementsAre(DiagMessage( 153 "CompilationDatabase must be an absolute path, because this " 154 "fragment is not associated with any directory."))); 155 156 // Relative path allowed if directory is set. 157 Frag.Source.Directory = testRoot(); 158 EXPECT_TRUE(compileAndApply()); 159 EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, 160 Config::CDBSearchSpec::FixedDir); 161 EXPECT_EQ(Conf.CompileFlags.CDBSearch.FixedCDBPath, testPath("Something")); 162 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 163 164 // Absolute path allowed. 165 Frag.Source.Directory.clear(); 166 Frag.CompileFlags.CompilationDatabase.emplace(testPath("Something2")); 167 EXPECT_TRUE(compileAndApply()); 168 EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, 169 Config::CDBSearchSpec::FixedDir); 170 EXPECT_EQ(Conf.CompileFlags.CDBSearch.FixedCDBPath, testPath("Something2")); 171 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 172 } 173 174 TEST_F(ConfigCompileTests, Index) { 175 Frag.Index.Background.emplace("Skip"); 176 EXPECT_TRUE(compileAndApply()); 177 EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip); 178 179 Frag = {}; 180 Frag.Index.Background.emplace("Foo"); 181 EXPECT_TRUE(compileAndApply()); 182 EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Build) 183 << "by default"; 184 EXPECT_THAT( 185 Diags.Diagnostics, 186 ElementsAre(DiagMessage( 187 "Invalid Background value 'Foo'. Valid values are Build, Skip."))); 188 } 189 190 TEST_F(ConfigCompileTests, PathSpecMatch) { 191 auto BarPath = llvm::sys::path::convert_to_slash(testPath("foo/bar.h")); 192 Parm.Path = BarPath; 193 194 struct { 195 std::string Directory; 196 std::string PathSpec; 197 bool ShouldMatch; 198 } Cases[] = { 199 { 200 // Absolute path matches. 201 "", 202 llvm::sys::path::convert_to_slash(testPath("foo/bar.h")), 203 true, 204 }, 205 { 206 // Absolute path fails. 207 "", 208 llvm::sys::path::convert_to_slash(testPath("bar/bar.h")), 209 false, 210 }, 211 { 212 // Relative should fail to match as /foo/bar.h doesn't reside under 213 // /baz/. 214 testPath("baz"), 215 "bar\\.h", 216 false, 217 }, 218 { 219 // Relative should pass with /foo as directory. 220 testPath("foo"), 221 "bar\\.h", 222 true, 223 }, 224 }; 225 226 // PathMatch 227 for (const auto &Case : Cases) { 228 Frag = {}; 229 Frag.If.PathMatch.emplace_back(Case.PathSpec); 230 Frag.Source.Directory = Case.Directory; 231 EXPECT_EQ(compileAndApply(), Case.ShouldMatch); 232 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 233 } 234 235 // PathEclude 236 for (const auto &Case : Cases) { 237 SCOPED_TRACE(Case.Directory); 238 SCOPED_TRACE(Case.PathSpec); 239 Frag = {}; 240 Frag.If.PathExclude.emplace_back(Case.PathSpec); 241 Frag.Source.Directory = Case.Directory; 242 EXPECT_NE(compileAndApply(), Case.ShouldMatch); 243 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 244 } 245 } 246 247 TEST_F(ConfigCompileTests, DiagnosticsIncludeCleaner) { 248 // Defaults to None. 249 EXPECT_TRUE(compileAndApply()); 250 EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, 251 Config::UnusedIncludesPolicy::None); 252 253 Frag = {}; 254 Frag.Diagnostics.UnusedIncludes.emplace("None"); 255 EXPECT_TRUE(compileAndApply()); 256 EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, 257 Config::UnusedIncludesPolicy::None); 258 259 Frag = {}; 260 Frag.Diagnostics.UnusedIncludes.emplace("Strict"); 261 EXPECT_TRUE(compileAndApply()); 262 EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, 263 Config::UnusedIncludesPolicy::Strict); 264 } 265 266 TEST_F(ConfigCompileTests, DiagnosticSuppression) { 267 Frag.Diagnostics.Suppress.emplace_back("bugprone-use-after-move"); 268 Frag.Diagnostics.Suppress.emplace_back("unreachable-code"); 269 Frag.Diagnostics.Suppress.emplace_back("-Wunused-variable"); 270 Frag.Diagnostics.Suppress.emplace_back("typecheck_bool_condition"); 271 Frag.Diagnostics.Suppress.emplace_back("err_unexpected_friend"); 272 Frag.Diagnostics.Suppress.emplace_back("warn_alloca"); 273 EXPECT_TRUE(compileAndApply()); 274 EXPECT_THAT(Conf.Diagnostics.Suppress.keys(), 275 UnorderedElementsAre("bugprone-use-after-move", 276 "unreachable-code", "unused-variable", 277 "typecheck_bool_condition", 278 "unexpected_friend", "warn_alloca")); 279 EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_unreachable, 280 Conf.Diagnostics.Suppress)); 281 // Subcategory not respected/suppressed. 282 EXPECT_FALSE(isBuiltinDiagnosticSuppressed(diag::warn_unreachable_break, 283 Conf.Diagnostics.Suppress)); 284 EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_unused_variable, 285 Conf.Diagnostics.Suppress)); 286 EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_typecheck_bool_condition, 287 Conf.Diagnostics.Suppress)); 288 EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_unexpected_friend, 289 Conf.Diagnostics.Suppress)); 290 EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_alloca, 291 Conf.Diagnostics.Suppress)); 292 293 Frag.Diagnostics.Suppress.emplace_back("*"); 294 EXPECT_TRUE(compileAndApply()); 295 EXPECT_TRUE(Conf.Diagnostics.SuppressAll); 296 EXPECT_THAT(Conf.Diagnostics.Suppress, IsEmpty()); 297 } 298 299 TEST_F(ConfigCompileTests, Tidy) { 300 auto &Tidy = Frag.Diagnostics.ClangTidy; 301 Tidy.Add.emplace_back("bugprone-use-after-move"); 302 Tidy.Add.emplace_back("llvm-*"); 303 Tidy.Remove.emplace_back("llvm-include-order"); 304 Tidy.Remove.emplace_back("readability-*"); 305 Tidy.CheckOptions.emplace_back( 306 std::make_pair(std::string("StrictMode"), std::string("true"))); 307 Tidy.CheckOptions.emplace_back(std::make_pair( 308 std::string("example-check.ExampleOption"), std::string("0"))); 309 EXPECT_TRUE(compileAndApply()); 310 EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.size(), 2U); 311 EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup("StrictMode"), 312 "true"); 313 EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup( 314 "example-check.ExampleOption"), 315 "0"); 316 #if CLANGD_TIDY_CHECKS 317 EXPECT_EQ( 318 Conf.Diagnostics.ClangTidy.Checks, 319 "bugprone-use-after-move,llvm-*,-llvm-include-order,-readability-*"); 320 EXPECT_THAT(Diags.Diagnostics, IsEmpty()); 321 #else // !CLANGD_TIDY_CHECKS 322 EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "llvm-*,-readability-*"); 323 EXPECT_THAT( 324 Diags.Diagnostics, 325 ElementsAre( 326 DiagMessage( 327 "clang-tidy check 'bugprone-use-after-move' was not found"), 328 DiagMessage("clang-tidy check 'llvm-include-order' was not found"))); 329 #endif 330 } 331 332 TEST_F(ConfigCompileTests, TidyBadChecks) { 333 auto &Tidy = Frag.Diagnostics.ClangTidy; 334 Tidy.Add.emplace_back("unknown-check"); 335 Tidy.Remove.emplace_back("*"); 336 Tidy.Remove.emplace_back("llvm-includeorder"); 337 EXPECT_TRUE(compileAndApply()); 338 // Ensure bad checks are stripped from the glob. 339 EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "-*"); 340 EXPECT_THAT( 341 Diags.Diagnostics, 342 ElementsAre( 343 AllOf(DiagMessage("clang-tidy check 'unknown-check' was not found"), 344 DiagKind(llvm::SourceMgr::DK_Warning)), 345 AllOf( 346 DiagMessage("clang-tidy check 'llvm-includeorder' was not found"), 347 DiagKind(llvm::SourceMgr::DK_Warning)))); 348 } 349 350 TEST_F(ConfigCompileTests, ExternalServerNeedsTrusted) { 351 Fragment::IndexBlock::ExternalBlock External; 352 External.Server.emplace("xxx"); 353 Frag.Index.External = std::move(External); 354 compileAndApply(); 355 EXPECT_THAT( 356 Diags.Diagnostics, 357 ElementsAre(DiagMessage( 358 "Remote index may not be specified by untrusted configuration. " 359 "Copy this into user config to use it."))); 360 EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 361 } 362 363 TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) { 364 Frag.Source.Trusted = true; 365 Fragment::IndexBlock::ExternalBlock External; 366 External.File.emplace(""); 367 External.Server.emplace(""); 368 Frag.Index.External = std::move(External); 369 compileAndApply(); 370 #ifdef CLANGD_ENABLE_REMOTE 371 EXPECT_THAT( 372 Diags.Diagnostics, 373 Contains( 374 AllOf(DiagMessage("Exactly one of File, Server or None must be set."), 375 DiagKind(llvm::SourceMgr::DK_Error)))); 376 #else 377 ASSERT_TRUE(Conf.Index.External.hasValue()); 378 EXPECT_EQ(Conf.Index.External->Kind, Config::ExternalIndexSpec::File); 379 #endif 380 } 381 382 TEST_F(ConfigCompileTests, ExternalBlockDisableWithNone) { 383 compileAndApply(); 384 EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 385 386 Fragment::IndexBlock::ExternalBlock External; 387 External.IsNone = true; 388 Frag.Index.External = std::move(External); 389 compileAndApply(); 390 EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 391 } 392 393 TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) { 394 Frag.Index.External.emplace(Fragment::IndexBlock::ExternalBlock{}); 395 compileAndApply(); 396 EXPECT_THAT( 397 Diags.Diagnostics, 398 Contains( 399 AllOf(DiagMessage("Exactly one of File, Server or None must be set."), 400 DiagKind(llvm::SourceMgr::DK_Error)))); 401 } 402 403 TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) { 404 auto BazPath = testPath("foo/bar/baz.h", llvm::sys::path::Style::posix); 405 Parm.Path = BazPath; 406 Frag.Index.Background.emplace("Build"); 407 Fragment::IndexBlock::ExternalBlock External; 408 External.File.emplace(testPath("foo")); 409 External.MountPoint.emplace( 410 testPath("foo/bar", llvm::sys::path::Style::posix)); 411 Frag.Index.External = std::move(External); 412 compileAndApply(); 413 EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip); 414 } 415 416 TEST_F(ConfigCompileTests, ExternalBlockMountPoint) { 417 auto GetFrag = [](llvm::StringRef Directory, 418 llvm::Optional<const char *> MountPoint) { 419 Fragment Frag; 420 Frag.Source.Directory = Directory.str(); 421 Fragment::IndexBlock::ExternalBlock External; 422 External.File.emplace(testPath("foo")); 423 if (MountPoint) 424 External.MountPoint.emplace(*MountPoint); 425 Frag.Index.External = std::move(External); 426 return Frag; 427 }; 428 429 auto BarPath = testPath("foo/bar.h", llvm::sys::path::Style::posix); 430 BarPath = llvm::sys::path::convert_to_slash(BarPath); 431 Parm.Path = BarPath; 432 // Non-absolute MountPoint without a directory raises an error. 433 Frag = GetFrag("", "foo"); 434 compileAndApply(); 435 ASSERT_THAT( 436 Diags.Diagnostics, 437 ElementsAre( 438 AllOf(DiagMessage("MountPoint must be an absolute path, because this " 439 "fragment is not associated with any directory."), 440 DiagKind(llvm::SourceMgr::DK_Error)))); 441 EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 442 443 auto FooPath = testPath("foo/", llvm::sys::path::Style::posix); 444 FooPath = llvm::sys::path::convert_to_slash(FooPath); 445 // Ok when relative. 446 Frag = GetFrag(testRoot(), "foo/"); 447 compileAndApply(); 448 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 449 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); 450 EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); 451 452 // None defaults to ".". 453 Frag = GetFrag(FooPath, llvm::None); 454 compileAndApply(); 455 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 456 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); 457 EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); 458 459 // Without a file, external index is empty. 460 Parm.Path = ""; 461 Frag = GetFrag("", FooPath.c_str()); 462 compileAndApply(); 463 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 464 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 465 466 // File outside MountPoint, no index. 467 auto BazPath = testPath("bar/baz.h", llvm::sys::path::Style::posix); 468 BazPath = llvm::sys::path::convert_to_slash(BazPath); 469 Parm.Path = BazPath; 470 Frag = GetFrag("", FooPath.c_str()); 471 compileAndApply(); 472 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 473 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 474 475 // File under MountPoint, index should be set. 476 BazPath = testPath("foo/baz.h", llvm::sys::path::Style::posix); 477 BazPath = llvm::sys::path::convert_to_slash(BazPath); 478 Parm.Path = BazPath; 479 Frag = GetFrag("", FooPath.c_str()); 480 compileAndApply(); 481 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 482 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); 483 EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); 484 485 // Only matches case-insensitively. 486 BazPath = testPath("fOo/baz.h", llvm::sys::path::Style::posix); 487 BazPath = llvm::sys::path::convert_to_slash(BazPath); 488 Parm.Path = BazPath; 489 490 FooPath = testPath("FOO/", llvm::sys::path::Style::posix); 491 FooPath = llvm::sys::path::convert_to_slash(FooPath); 492 Frag = GetFrag("", FooPath.c_str()); 493 compileAndApply(); 494 ASSERT_THAT(Diags.Diagnostics, IsEmpty()); 495 #ifdef CLANGD_PATH_CASE_INSENSITIVE 496 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); 497 EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); 498 #else 499 ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); 500 #endif 501 } 502 503 TEST_F(ConfigCompileTests, AllScopes) { 504 // Defaults to true. 505 EXPECT_TRUE(compileAndApply()); 506 EXPECT_TRUE(Conf.Completion.AllScopes); 507 508 Frag = {}; 509 Frag.Completion.AllScopes = false; 510 EXPECT_TRUE(compileAndApply()); 511 EXPECT_FALSE(Conf.Completion.AllScopes); 512 513 Frag = {}; 514 Frag.Completion.AllScopes = true; 515 EXPECT_TRUE(compileAndApply()); 516 EXPECT_TRUE(Conf.Completion.AllScopes); 517 } 518 } // namespace 519 } // namespace config 520 } // namespace clangd 521 } // namespace clang 522