[clangd] Improve output of semantic highlighting tests in case of failures

Summary:
Instead of matching lists of highlightings, we annotate input code with
resulting highlightings and diff it against the expected annotated input.

In case of failures, this produces much nicer output in form of text-based
diffs.

Reviewers: hokein

Reviewed By: hokein

Subscribers: nridge, MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D67274

git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@371373 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/clangd/SemanticHighlighting.cpp b/clangd/SemanticHighlighting.cpp
index 268f311..6316c1e 100644
--- a/clangd/SemanticHighlighting.cpp
+++ b/clangd/SemanticHighlighting.cpp
@@ -351,6 +351,43 @@
 }
 } // namespace
 
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K) {
+  switch (K) {
+  case HighlightingKind::Variable:
+    return OS << "Variable";
+  case HighlightingKind::LocalVariable:
+    return OS << "LocalVariable";
+  case HighlightingKind::Parameter:
+    return OS << "Parameter";
+  case HighlightingKind::Function:
+    return OS << "Function";
+  case HighlightingKind::Method:
+    return OS << "Method";
+  case HighlightingKind::StaticMethod:
+    return OS << "StaticMethod";
+  case HighlightingKind::Field:
+    return OS << "Field";
+  case HighlightingKind::StaticField:
+    return OS << "StaticField";
+  case HighlightingKind::Class:
+    return OS << "Class";
+  case HighlightingKind::Enum:
+    return OS << "Enum";
+  case HighlightingKind::EnumConstant:
+    return OS << "EnumConstant";
+  case HighlightingKind::Namespace:
+    return OS << "Namespace";
+  case HighlightingKind::TemplateParameter:
+    return OS << "TemplateParameter";
+  case HighlightingKind::Primitive:
+    return OS << "Primitive";
+  case HighlightingKind::Macro:
+    return OS << "Macro";
+  case HighlightingKind::NumKinds:
+    llvm_unreachable("NumKinds is not a valid HighlightingKind");
+  }
+}
+
 std::vector<LineHighlightings>
 diffHighlightings(ArrayRef<HighlightingToken> New,
                   ArrayRef<HighlightingToken> Old) {
diff --git a/clangd/SemanticHighlighting.h b/clangd/SemanticHighlighting.h
index f78763f..3fcb611 100644
--- a/clangd/SemanticHighlighting.h
+++ b/clangd/SemanticHighlighting.h
@@ -18,6 +18,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICHIGHLIGHTING_H
 
 #include "Protocol.h"
+#include "llvm/Support/raw_ostream.h"
 
 namespace clang {
 namespace clangd {
@@ -42,6 +43,7 @@
 
   NumKinds,
 };
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K);
 
 // Contains all information needed for the highlighting a token.
 struct HighlightingToken {
diff --git a/clangd/unittests/SemanticHighlightingTests.cpp b/clangd/unittests/SemanticHighlightingTests.cpp
index cc60e62..6630396 100644
--- a/clangd/unittests/SemanticHighlightingTests.cpp
+++ b/clangd/unittests/SemanticHighlightingTests.cpp
@@ -10,9 +10,14 @@
 #include "ClangdServer.h"
 #include "Protocol.h"
 #include "SemanticHighlighting.h"
+#include "SourceCode.h"
 #include "TestFS.h"
 #include "TestTU.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
 #include "gmock/gmock.h"
+#include <algorithm>
 
 namespace clang {
 namespace clangd {
@@ -59,6 +64,36 @@
   return ExpectedTokens;
 }
 
+/// Annotates the input code with provided semantic highlightings. Results look
+/// something like:
+///   class $Class[[X]] {
+///     $Primitive[[int]] $Field[[a]] = 0;
+///   };
+std::string annotate(llvm::StringRef Input,
+                     llvm::ArrayRef<HighlightingToken> Tokens) {
+  assert(std::is_sorted(
+      Tokens.begin(), Tokens.end(),
+      [](const HighlightingToken &L, const HighlightingToken &R) {
+        return L.R.start < R.R.start;
+      }));
+
+  std::string Result;
+  unsigned NextChar = 0;
+  for (auto &T : Tokens) {
+    unsigned StartOffset = llvm::cantFail(positionToOffset(Input, T.R.start));
+    unsigned EndOffset = llvm::cantFail(positionToOffset(Input, T.R.end));
+    assert(StartOffset <= EndOffset);
+    assert(NextChar <= StartOffset);
+
+    Result += Input.substr(NextChar, StartOffset - NextChar);
+    Result += llvm::formatv("${0}[[{1}]]", T.Kind,
+                            Input.substr(StartOffset, EndOffset - StartOffset));
+    NextChar = EndOffset;
+  }
+  Result += Input.substr(NextChar);
+  return Result;
+}
+
 void checkHighlightings(llvm::StringRef Code,
                         std::vector<std::pair</*FileName*/ llvm::StringRef,
                                               /*FileContent*/ llvm::StringRef>>
@@ -68,8 +103,8 @@
   for (auto File : AdditionalFiles)
     TU.AdditionalFiles.insert({File.first, File.second});
   auto AST = TU.build();
-  std::vector<HighlightingToken> ActualTokens = getSemanticHighlightings(AST);
-  EXPECT_THAT(ActualTokens, getExpectedTokens(Test)) << Code;
+
+  EXPECT_EQ(Code, annotate(Test.code(), getSemanticHighlightings(AST)));
 }
 
 // Any annotations in OldCode and NewCode are converted into their corresponding