[clangd] Add ClangdUnit diagnostics tests using annotated code.

Summary:
This adds checks that our diagnostics emit correct ranges in a bunch of cases,
as promised in D41118.

The diagnostics-preamble test is also converted and extended to be a little more
precise.

diagnostics.test stays around as the smoke test for this feature.

Reviewers: ilya-biryukov

Subscribers: klimek, mgorny, cfe-commits

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

llvm-svn: 323448
This commit is contained in:
Sam McCall 2018-01-25 17:29:17 +00:00
parent 102d4b59f9
commit 034e11aca5
5 changed files with 161 additions and 22 deletions

View File

@ -137,6 +137,12 @@ json::Expr toJSON(const TextEdit &P) {
};
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const TextEdit &TE) {
OS << TE.range << " => \"";
PrintEscapedString(TE.newText, OS);
return OS << '"';
}
bool fromJSON(const json::Expr &E, TraceLevel &Out) {
if (auto S = E.asString()) {
if (*S == "off") {
@ -255,6 +261,28 @@ bool fromJSON(const json::Expr &Params, CodeActionContext &R) {
return O && O.map("diagnostics", R.diagnostics);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diagnostic &D) {
OS << D.range << " [";
switch (D.severity) {
case 1:
OS << "error";
break;
case 2:
OS << "warning";
break;
case 3:
OS << "note";
break;
case 4:
OS << "remark";
break;
default:
OS << "diagnostic";
break;
}
return OS << '(' << D.severity << "): " << D.message << "]";
}
bool fromJSON(const json::Expr &Params, CodeActionParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&

View File

@ -150,6 +150,7 @@ struct TextEdit {
};
bool fromJSON(const json::Expr &, TextEdit &);
json::Expr toJSON(const TextEdit &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &);
struct TextDocumentItem {
/// The text document's URI.
@ -341,6 +342,7 @@ struct LSPDiagnosticCompare {
}
};
bool fromJSON(const json::Expr &, Diagnostic &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Diagnostic &);
struct CodeActionContext {
/// An array of diagnostics.

View File

@ -1,22 +0,0 @@
# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# RUN: clangd -pretty -run-synchronously -pch-storage=memory < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
Content-Length: 206
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#ifndef FOO\n#define FOO\nint a;\n#else\nint a = b;#endif\n\n\n"}}}
# CHECK: "method": "textDocument/publishDiagnostics",
# CHECK-NEXT: "params": {
# CHECK-NEXT: "diagnostics": [],
# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
# CHECK-NEXT: }
Content-Length: 58
{"jsonrpc":"2.0","id":2,"method":"shutdown","params":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}

View File

@ -11,6 +11,7 @@ include_directories(
add_extra_unittest(ClangdTests
Annotations.cpp
ClangdTests.cpp
ClangdUnitTests.cpp
CodeCompleteTests.cpp
CodeCompletionStringsTests.cpp
ContextTests.cpp

View File

@ -0,0 +1,130 @@
//===-- ClangdUnitTests.cpp - ClangdUnit tests ------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ClangdUnit.h"
#include "Annotations.h"
#include "TestFS.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Frontend/Utils.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
using namespace llvm;
void PrintTo(const DiagWithFixIts &D, std::ostream *O) {
llvm::raw_os_ostream OS(*O);
OS << D.Diag;
if (!D.FixIts.empty()) {
OS << " {";
const char *Sep = "";
for (const auto &F : D.FixIts) {
OS << Sep << F;
Sep = ", ";
}
OS << "}";
}
}
namespace {
using testing::ElementsAre;
// FIXME: this is duplicated with FileIndexTests. Share it.
ParsedAST build(StringRef Code, std::vector<const char*> Flags = {}) {
std::vector<const char*> Cmd = {"clang", "main.cpp"};
Cmd.insert(Cmd.begin() + 1, Flags.begin(), Flags.end());
auto CI = createInvocationFromCommandLine(Cmd);
auto Buf = MemoryBuffer::getMemBuffer(Code);
auto AST = ParsedAST::Build(
Context::empty(), std::move(CI), nullptr, std::move(Buf),
std::make_shared<PCHContainerOperations>(), vfs::getRealFileSystem());
assert(AST.hasValue());
return std::move(*AST);
}
MATCHER_P2(Diag, Range, Message,
"Diagnostic at " + llvm::to_string(Range) + " = [" + Message + "]") {
return arg.Diag.range == Range && arg.Diag.message == Message &&
arg.FixIts.empty();
}
MATCHER_P3(Fix, Range, Replacement, Message,
"Fix " + llvm::to_string(Range) + " => " +
testing::PrintToString(Replacement) + " = [" + Message + "]") {
return arg.Diag.range == Range && arg.Diag.message == Message &&
arg.FixIts.size() == 1 && arg.FixIts[0].range == Range &&
arg.FixIts[0].newText == Replacement;
}
TEST(DiagnosticsTest, DiagnosticRanges) {
// Check we report correct ranges, including various edge-cases.
Annotations Test(R"cpp(
void $decl[[foo]]();
int main() {
$typo[[go\
o]]();
foo()$semicolon[[]]
$unk[[unknown]]();
}
)cpp");
llvm::errs() << Test.code();
EXPECT_THAT(
build(Test.code()).getDiagnostics(),
ElementsAre(
// This range spans lines.
Fix(Test.range("typo"), "foo",
"use of undeclared identifier 'goo'; did you mean 'foo'?"),
// This is a pretty normal range.
Diag(Test.range("decl"), "'foo' declared here"),
// This range is zero-width, and at the end of a line.
Fix(Test.range("semicolon"), ";",
"expected ';' after expression"),
// This range isn't provided by clang, we expand to the token.
Diag(Test.range("unk"),
"use of undeclared identifier 'unknown'")));
}
TEST(DiagnosticsTest, FlagsMatter) {
Annotations Test("[[void]] main() {}");
EXPECT_THAT(
build(Test.code()).getDiagnostics(),
ElementsAre(Fix(Test.range(), "int", "'main' must return 'int'")));
// Same code built as C gets different diagnostics.
EXPECT_THAT(
build(Test.code(), {"-x", "c"}).getDiagnostics(),
ElementsAre(
// FIXME: ideally this would be one diagnostic with a named FixIt.
Diag(Test.range(), "return type of 'main' is not 'int'"),
Fix(Test.range(), "int", "change return type to 'int'")));
}
TEST(DiagnosticsTest, Preprocessor) {
// This looks like a preamble, but there's an #else in the middle!
// Check that:
// - the #else doesn't generate diagnostics (we had this bug)
// - we get diagnostics from the taken branch
// - we get no diagnostics from the not taken branch
Annotations Test(R"cpp(
#ifndef FOO
#define FOO
int a = [[b]];
#else
int x = y;
#endif
)cpp");
EXPECT_THAT(
build(Test.code()).getDiagnostics(),
ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
}
} // namespace
} // namespace clangd
} // namespace clang