brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.5 KiB · 879a179 Raw
510 lines · cpp
1//===-- QualityTests.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-exception6//7//===----------------------------------------------------------------------===//8//9// Evaluating scoring functions isn't a great fit for assert-based tests.10// For interesting cases, both exact scores and "X beats Y" are too brittle to11// make good hard assertions.12//13// Here we test the signal extraction and sanity-check that signals point in14// the right direction. This should be supplemented by quality metrics which15// we can compute from a corpus of queries and preferred rankings.16//17//===----------------------------------------------------------------------===//18 19#include "FileDistance.h"20#include "Quality.h"21#include "TestFS.h"22#include "TestTU.h"23#include "index/FileIndex.h"24#include "clang/AST/Decl.h"25#include "clang/AST/DeclCXX.h"26#include "clang/Sema/CodeCompleteConsumer.h"27#include "llvm/Support/Casting.h"28#include "gmock/gmock.h"29#include "gtest/gtest.h"30#include <vector>31 32namespace clang {33namespace clangd {34 35// Force the unittest URI scheme to be linked,36[[maybe_unused]] static int UnittestSchemeAnchorDest =37    UnittestSchemeAnchorSource;38 39namespace {40 41TEST(QualityTests, SymbolQualitySignalExtraction) {42  auto Header = TestTU::withHeaderCode(R"cpp(43    int _X;44 45    [[deprecated]]46    int _f() { return _X; }47 48    #define DECL_NAME(x, y) x##_##y##_Decl49    #define DECL(x, y) class DECL_NAME(x, y) {};50    DECL(X, Y); // X_Y_Decl51  )cpp");52 53  auto Symbols = Header.headerSymbols();54  auto AST = Header.build();55 56  SymbolQualitySignals Quality;57  Quality.merge(findSymbol(Symbols, "X_Y_Decl"));58  EXPECT_TRUE(Quality.ImplementationDetail);59 60  Symbol F = findSymbol(Symbols, "_f");61  F.References = 24; // TestTU doesn't count references, so fake it.62  Quality = {};63  Quality.merge(F);64  EXPECT_TRUE(Quality.Deprecated);65  EXPECT_FALSE(Quality.ReservedName);66  EXPECT_EQ(Quality.References, 24u);67  EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function);68 69  Quality = {};70  Quality.merge(CodeCompletionResult(&findDecl(AST, "_f"), /*Priority=*/42));71  EXPECT_TRUE(Quality.Deprecated);72  EXPECT_FALSE(Quality.ReservedName);73  EXPECT_EQ(Quality.References, SymbolQualitySignals().References);74  EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function);75 76  Quality = {};77  Quality.merge(CodeCompletionResult("if"));78  EXPECT_EQ(Quality.Category, SymbolQualitySignals::Keyword);79 80  // Testing ReservedName in main file, we don't index those symbols in headers.81  auto MainAST = TestTU::withCode("int _X;").build();82  SymbolSlab MainSymbols = std::get<0>(indexMainDecls(MainAST));83 84  Quality = {};85  Quality.merge(findSymbol(MainSymbols, "_X"));86  EXPECT_FALSE(Quality.Deprecated);87  EXPECT_FALSE(Quality.ImplementationDetail);88  EXPECT_TRUE(Quality.ReservedName);89}90 91TEST(QualityTests, SymbolRelevanceSignalExtraction) {92  TestTU Test;93  Test.HeaderCode = R"cpp(94  int header();95  int header_main();96 97  namespace hdr { class Bar {}; } // namespace hdr98 99  #define DEFINE_FLAG(X) \100  namespace flags { \101  int FLAGS_##X; \102  } \103 104  DEFINE_FLAG(FOO)105  )cpp";106  Test.Code = R"cpp(107  using hdr::Bar;108 109  using flags::FLAGS_FOO;110 111  int ::header_main() { return 0; }112  int main();113 114  [[deprecated]]115  int deprecated() { return 0; }116 117  namespace { struct X { void y() { int z; } }; }118  struct S{};119  )cpp";120  auto AST = Test.build();121 122  SymbolRelevanceSignals Relevance;123  Relevance.merge(CodeCompletionResult(&findDecl(AST, "deprecated"),124                                       /*Priority=*/42,125                                       /*Qualifier=*/std::nullopt,126                                       /*QualifierIsInformative=*/false,127                                       /*Accessible=*/false));128  EXPECT_EQ(Relevance.NameMatch, SymbolRelevanceSignals().NameMatch);129  EXPECT_TRUE(Relevance.Forbidden);130  EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::GlobalScope);131 132  Relevance = {};133  Relevance.merge(CodeCompletionResult(&findDecl(AST, "main"), 42));134  EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)135      << "Decl in current file";136  Relevance = {};137  Relevance.merge(CodeCompletionResult(&findDecl(AST, "header"), 42));138  EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 0.6f) << "Decl from header";139  Relevance = {};140  Relevance.merge(CodeCompletionResult(&findDecl(AST, "header_main"), 42));141  EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)142      << "Current file and header";143 144  auto ConstructShadowDeclCompletionResult = [&](const std::string DeclName) {145    auto *Shadow =146        *dyn_cast<UsingDecl>(&findDecl(AST, [&](const NamedDecl &ND) {147           if (const UsingDecl *Using = dyn_cast<UsingDecl>(&ND))148             if (Using->shadow_size() &&149                 Using->getQualifiedNameAsString() == DeclName)150               return true;151           return false;152         }))->shadow_begin();153    CodeCompletionResult Result(Shadow->getTargetDecl(), 42);154    Result.ShadowDecl = Shadow;155    return Result;156  };157 158  Relevance = {};159  Relevance.merge(ConstructShadowDeclCompletionResult("Bar"));160  EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)161      << "Using declaration in main file";162  Relevance.merge(ConstructShadowDeclCompletionResult("FLAGS_FOO"));163  EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)164      << "Using declaration in main file";165 166  Relevance = {};167  Relevance.merge(CodeCompletionResult(&findUnqualifiedDecl(AST, "X"), 42));168  EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FileScope);169  Relevance = {};170  Relevance.merge(CodeCompletionResult(&findUnqualifiedDecl(AST, "y"), 42));171  EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::ClassScope);172  Relevance = {};173  Relevance.merge(CodeCompletionResult(&findUnqualifiedDecl(AST, "z"), 42));174  EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FunctionScope);175  // The injected class name is treated as the outer class name.176  Relevance = {};177  Relevance.merge(CodeCompletionResult(&findDecl(AST, "S::S"), 42));178  EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::GlobalScope);179 180  Relevance = {};181  EXPECT_FALSE(Relevance.InBaseClass);182  auto BaseMember = CodeCompletionResult(&findUnqualifiedDecl(AST, "y"), 42);183  BaseMember.InBaseClass = true;184  Relevance.merge(BaseMember);185  EXPECT_TRUE(Relevance.InBaseClass);186 187  auto Index = Test.index();188  FuzzyFindRequest Req;189  Req.Query = "X";190  Req.AnyScope = true;191  bool Matched = false;192  Index->fuzzyFind(Req, [&](const Symbol &S) {193    Matched = true;194    Relevance = {};195    Relevance.merge(S);196    EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FileScope);197  });198  EXPECT_TRUE(Matched);199}200 201// Do the signals move the scores in the direction we expect?202TEST(QualityTests, SymbolQualitySignalsSanity) {203  SymbolQualitySignals Default;204  EXPECT_EQ(Default.evaluateHeuristics(), 1);205 206  SymbolQualitySignals Deprecated;207  Deprecated.Deprecated = true;208  EXPECT_LT(Deprecated.evaluateHeuristics(), Default.evaluateHeuristics());209 210  SymbolQualitySignals ReservedName;211  ReservedName.ReservedName = true;212  EXPECT_LT(ReservedName.evaluateHeuristics(), Default.evaluateHeuristics());213 214  SymbolQualitySignals ImplementationDetail;215  ImplementationDetail.ImplementationDetail = true;216  EXPECT_LT(ImplementationDetail.evaluateHeuristics(),217            Default.evaluateHeuristics());218 219  SymbolQualitySignals WithReferences, ManyReferences;220  WithReferences.References = 20;221  ManyReferences.References = 1000;222  EXPECT_GT(WithReferences.evaluateHeuristics(), Default.evaluateHeuristics());223  EXPECT_GT(ManyReferences.evaluateHeuristics(),224            WithReferences.evaluateHeuristics());225 226  SymbolQualitySignals Keyword, Variable, Macro, Constructor, Function,227      Destructor, Operator;228  Keyword.Category = SymbolQualitySignals::Keyword;229  Variable.Category = SymbolQualitySignals::Variable;230  Macro.Category = SymbolQualitySignals::Macro;231  Constructor.Category = SymbolQualitySignals::Constructor;232  Destructor.Category = SymbolQualitySignals::Destructor;233  Destructor.Category = SymbolQualitySignals::Destructor;234  Operator.Category = SymbolQualitySignals::Operator;235  Function.Category = SymbolQualitySignals::Function;236  EXPECT_GT(Variable.evaluateHeuristics(), Default.evaluateHeuristics());237  EXPECT_GT(Keyword.evaluateHeuristics(), Variable.evaluateHeuristics());238  EXPECT_LT(Macro.evaluateHeuristics(), Default.evaluateHeuristics());239  EXPECT_LT(Operator.evaluateHeuristics(), Default.evaluateHeuristics());240  EXPECT_LT(Constructor.evaluateHeuristics(), Function.evaluateHeuristics());241  EXPECT_LT(Destructor.evaluateHeuristics(), Constructor.evaluateHeuristics());242}243 244TEST(QualityTests, SymbolRelevanceSignalsSanity) {245  SymbolRelevanceSignals Default;246  EXPECT_EQ(Default.evaluateHeuristics(), 1);247 248  SymbolRelevanceSignals Forbidden;249  Forbidden.Forbidden = true;250  EXPECT_LT(Forbidden.evaluateHeuristics(), Default.evaluateHeuristics());251 252  SymbolRelevanceSignals PoorNameMatch;253  PoorNameMatch.NameMatch = 0.2f;254  EXPECT_LT(PoorNameMatch.evaluateHeuristics(), Default.evaluateHeuristics());255 256  SymbolRelevanceSignals WithSemaFileProximity;257  WithSemaFileProximity.SemaFileProximityScore = 0.2f;258  EXPECT_GT(WithSemaFileProximity.evaluateHeuristics(),259            Default.evaluateHeuristics());260 261  ScopeDistance ScopeProximity({"x::y::"});262 263  SymbolRelevanceSignals WithSemaScopeProximity;264  WithSemaScopeProximity.ScopeProximityMatch = &ScopeProximity;265  WithSemaScopeProximity.SemaSaysInScope = true;266  EXPECT_GT(WithSemaScopeProximity.evaluateHeuristics(),267            Default.evaluateHeuristics());268 269  SymbolRelevanceSignals WithIndexScopeProximity;270  WithIndexScopeProximity.ScopeProximityMatch = &ScopeProximity;271  WithIndexScopeProximity.SymbolScope = "x::";272  EXPECT_GT(WithSemaScopeProximity.evaluateHeuristics(),273            Default.evaluateHeuristics());274 275  SymbolRelevanceSignals IndexProximate;276  IndexProximate.SymbolURI = "unittest:/foo/bar.h";277  llvm::StringMap<SourceParams> ProxSources;278  ProxSources.try_emplace(testPath("foo/baz.h"));279  URIDistance Distance(ProxSources);280  IndexProximate.FileProximityMatch = &Distance;281  EXPECT_GT(IndexProximate.evaluateHeuristics(), Default.evaluateHeuristics());282  SymbolRelevanceSignals IndexDistant = IndexProximate;283  IndexDistant.SymbolURI = "unittest:/elsewhere/path.h";284  EXPECT_GT(IndexProximate.evaluateHeuristics(),285            IndexDistant.evaluateHeuristics())286      << IndexProximate << IndexDistant;287  EXPECT_GT(IndexDistant.evaluateHeuristics(), Default.evaluateHeuristics());288 289  SymbolRelevanceSignals Scoped;290  Scoped.Scope = SymbolRelevanceSignals::FileScope;291  EXPECT_LT(Scoped.evaluateHeuristics(), Default.evaluateHeuristics());292  Scoped.Query = SymbolRelevanceSignals::CodeComplete;293  EXPECT_GT(Scoped.evaluateHeuristics(), Default.evaluateHeuristics());294 295  SymbolRelevanceSignals Instance;296  Instance.IsInstanceMember = false;297  EXPECT_EQ(Instance.evaluateHeuristics(), Default.evaluateHeuristics());298  Instance.Context = CodeCompletionContext::CCC_DotMemberAccess;299  EXPECT_LT(Instance.evaluateHeuristics(), Default.evaluateHeuristics());300  Instance.IsInstanceMember = true;301  EXPECT_EQ(Instance.evaluateHeuristics(), Default.evaluateHeuristics());302 303  SymbolRelevanceSignals InBaseClass;304  InBaseClass.InBaseClass = true;305  EXPECT_LT(InBaseClass.evaluateHeuristics(), Default.evaluateHeuristics());306 307  llvm::StringSet<> Words = {"one", "two", "three"};308  SymbolRelevanceSignals WithoutMatchingWord;309  WithoutMatchingWord.ContextWords = &Words;310  WithoutMatchingWord.Name = "four";311  EXPECT_EQ(WithoutMatchingWord.evaluateHeuristics(),312            Default.evaluateHeuristics());313  SymbolRelevanceSignals WithMatchingWord;314  WithMatchingWord.ContextWords = &Words;315  WithMatchingWord.Name = "TheTwoTowers";316  EXPECT_GT(WithMatchingWord.evaluateHeuristics(),317            Default.evaluateHeuristics());318}319 320TEST(QualityTests, ScopeProximity) {321  SymbolRelevanceSignals Relevance;322  ScopeDistance ScopeProximity({"x::y::z::", "x::", "llvm::", ""});323  Relevance.ScopeProximityMatch = &ScopeProximity;324 325  Relevance.SymbolScope = "other::";326  float NotMatched = Relevance.evaluateHeuristics();327 328  Relevance.SymbolScope = "";329  float Global = Relevance.evaluateHeuristics();330  EXPECT_GT(Global, NotMatched);331 332  Relevance.SymbolScope = "llvm::";333  float NonParent = Relevance.evaluateHeuristics();334  EXPECT_GT(NonParent, Global);335 336  Relevance.SymbolScope = "x::";337  float GrandParent = Relevance.evaluateHeuristics();338  EXPECT_GT(GrandParent, Global);339 340  Relevance.SymbolScope = "x::y::";341  float Parent = Relevance.evaluateHeuristics();342  EXPECT_GT(Parent, GrandParent);343 344  Relevance.SymbolScope = "x::y::z::";345  float Enclosing = Relevance.evaluateHeuristics();346  EXPECT_GT(Enclosing, Parent);347}348 349TEST(QualityTests, SortText) {350  EXPECT_LT(sortText(std::numeric_limits<float>::infinity()),351            sortText(1000.2f));352  EXPECT_LT(sortText(1000.2f), sortText(1));353  EXPECT_LT(sortText(1), sortText(0.3f));354  EXPECT_LT(sortText(0.3f), sortText(0));355  EXPECT_LT(sortText(0), sortText(-10));356  EXPECT_LT(sortText(-10), sortText(-std::numeric_limits<float>::infinity()));357 358  EXPECT_LT(sortText(1, "z"), sortText(0, "a"));359  EXPECT_LT(sortText(0, "a"), sortText(0, "z"));360}361 362TEST(QualityTests, NoBoostForClassConstructor) {363  auto Header = TestTU::withHeaderCode(R"cpp(364    class Foo {365    public:366      Foo(int);367    };368  )cpp");369  auto Symbols = Header.headerSymbols();370  auto AST = Header.build();371 372  const NamedDecl *Foo = &findDecl(AST, "Foo");373  SymbolRelevanceSignals Cls;374  Cls.merge(CodeCompletionResult(Foo, /*Priority=*/0));375 376  const NamedDecl *CtorDecl = &findDecl(AST, [](const NamedDecl &ND) {377    return (ND.getQualifiedNameAsString() == "Foo::Foo") &&378           isa<CXXConstructorDecl>(&ND);379  });380  SymbolRelevanceSignals Ctor;381  Ctor.merge(CodeCompletionResult(CtorDecl, /*Priority=*/0));382 383  EXPECT_EQ(Cls.Scope, SymbolRelevanceSignals::GlobalScope);384  EXPECT_EQ(Ctor.Scope, SymbolRelevanceSignals::GlobalScope);385}386 387TEST(QualityTests, IsInstanceMember) {388  auto Header = TestTU::withHeaderCode(R"cpp(389    class Foo {390    public:391      static void foo() {}392 393      template <typename T> void tpl(T *t) {}394 395      void bar() {}396    };397  )cpp");398  auto Symbols = Header.headerSymbols();399 400  SymbolRelevanceSignals Rel;401  const Symbol &FooSym = findSymbol(Symbols, "Foo::foo");402  Rel.merge(FooSym);403  EXPECT_FALSE(Rel.IsInstanceMember);404  const Symbol &BarSym = findSymbol(Symbols, "Foo::bar");405  Rel.merge(BarSym);406  EXPECT_TRUE(Rel.IsInstanceMember);407 408  Rel.IsInstanceMember = false;409  const Symbol &TplSym = findSymbol(Symbols, "Foo::tpl");410  Rel.merge(TplSym);411  EXPECT_TRUE(Rel.IsInstanceMember);412 413  auto AST = Header.build();414  const NamedDecl *Foo = &findDecl(AST, "Foo::foo");415  const NamedDecl *Bar = &findDecl(AST, "Foo::bar");416  const NamedDecl *Tpl = &findDecl(AST, "Foo::tpl");417 418  Rel.IsInstanceMember = false;419  Rel.merge(CodeCompletionResult(Foo, /*Priority=*/0));420  EXPECT_FALSE(Rel.IsInstanceMember);421  Rel.merge(CodeCompletionResult(Bar, /*Priority=*/0));422  EXPECT_TRUE(Rel.IsInstanceMember);423  Rel.IsInstanceMember = false;424  Rel.merge(CodeCompletionResult(Tpl, /*Priority=*/0));425  EXPECT_TRUE(Rel.IsInstanceMember);426}427 428TEST(QualityTests, ConstructorDestructor) {429  auto Header = TestTU::withHeaderCode(R"cpp(430    class Foo {431    public:432      Foo(int);433      ~Foo();434    };435  )cpp");436  auto Symbols = Header.headerSymbols();437  auto AST = Header.build();438 439  const NamedDecl *CtorDecl = &findDecl(AST, [](const NamedDecl &ND) {440    return (ND.getQualifiedNameAsString() == "Foo::Foo") &&441           isa<CXXConstructorDecl>(&ND);442  });443  const NamedDecl *DtorDecl = &findDecl(AST, [](const NamedDecl &ND) {444    return (ND.getQualifiedNameAsString() == "Foo::~Foo") &&445           isa<CXXDestructorDecl>(&ND);446  });447 448  SymbolQualitySignals CtorQ;449  CtorQ.merge(CodeCompletionResult(CtorDecl, /*Priority=*/0));450  EXPECT_EQ(CtorQ.Category, SymbolQualitySignals::Constructor);451 452  CtorQ.Category = SymbolQualitySignals::Unknown;453  const Symbol &CtorSym = findSymbol(Symbols, "Foo::Foo");454  CtorQ.merge(CtorSym);455  EXPECT_EQ(CtorQ.Category, SymbolQualitySignals::Constructor);456 457  SymbolQualitySignals DtorQ;458  DtorQ.merge(CodeCompletionResult(DtorDecl, /*Priority=*/0));459  EXPECT_EQ(DtorQ.Category, SymbolQualitySignals::Destructor);460}461 462TEST(QualityTests, Operator) {463  auto Header = TestTU::withHeaderCode(R"cpp(464    class Foo {465    public:466      bool operator<(const Foo& f1);467    };468  )cpp");469  auto AST = Header.build();470 471  const NamedDecl *Operator = &findDecl(AST, [](const NamedDecl &ND) {472    if (const auto *OD = dyn_cast<FunctionDecl>(&ND))473      if (OD->isOverloadedOperator())474        return true;475    return false;476  });477  SymbolQualitySignals Q;478  Q.merge(CodeCompletionResult(Operator, /*Priority=*/0));479  EXPECT_EQ(Q.Category, SymbolQualitySignals::Operator);480}481 482TEST(QualityTests, ItemWithFixItsRankedDown) {483  CodeCompleteOptions Opts;484  Opts.IncludeFixIts = true;485 486  auto Header = TestTU::withHeaderCode(R"cpp(487        int x;488      )cpp");489  auto AST = Header.build();490 491  SymbolRelevanceSignals RelevanceWithFixIt;492  RelevanceWithFixIt.merge(CodeCompletionResult(493      &findDecl(AST, "x"), /*Priority=*/0, /*Qualifier=*/std::nullopt,494      /*QualifierIsInformative=*/false, /*Accessible=*/true, {FixItHint{}}));495  EXPECT_TRUE(RelevanceWithFixIt.NeedsFixIts);496 497  SymbolRelevanceSignals RelevanceWithoutFixIt;498  RelevanceWithoutFixIt.merge(CodeCompletionResult(499      &findDecl(AST, "x"), /*Priority=*/0, /*Qualifier=*/std::nullopt,500      /*QualifierIsInformative=*/false, /*Accessible=*/true, {}));501  EXPECT_FALSE(RelevanceWithoutFixIt.NeedsFixIts);502 503  EXPECT_LT(RelevanceWithFixIt.evaluateHeuristics(),504            RelevanceWithoutFixIt.evaluateHeuristics());505}506 507} // namespace508} // namespace clangd509} // namespace clang510