[clang] a7691de - [Testing] TestAST, a helper for writing straight-line AST tests
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 21 12:46:53 PDT 2022
Author: Sam McCall
Date: 2022-04-21T21:46:45+02:00
New Revision: a7691dee2d3c0ea3f9f4d14c7e52f1359f23671c
URL: https://github.com/llvm/llvm-project/commit/a7691dee2d3c0ea3f9f4d14c7e52f1359f23671c
DIFF: https://github.com/llvm/llvm-project/commit/a7691dee2d3c0ea3f9f4d14c7e52f1359f23671c.diff
LOG: [Testing] TestAST, a helper for writing straight-line AST tests
Tests that need ASTs have to deal with the awkward control flow of
FrontendAction in some way. There are a few idioms used:
- don't bother with unit tests, use clang -dump-ast
- create an ASTConsumer by hand, which is bulky
- use ASTMatchFinder - works pretty well if matchers are actually
needed, very strange if they are not
- use ASTUnit - this yields nice straight-line code, but ASTUnit is a
terrifically complicated library not designed for this purpose
TestAST provides a very simple way to write straight-line tests: specify
the code/flags and it provides an AST that is kept alive until the
object is destroyed.
It's loosely modeled after TestTU in clangd, which we've successfully
used for a variety of tests.
I've updated a couple of clang tests to use this helper, IMO they're clearer.
Differential Revision: https://reviews.llvm.org/D123668
Added:
clang/include/clang/Testing/TestAST.h
clang/lib/Testing/TestAST.cpp
Modified:
clang/include/clang/Basic/Diagnostic.h
clang/include/clang/Testing/CommandLineArgs.h
clang/lib/Basic/Diagnostic.cpp
clang/lib/Testing/CMakeLists.txt
clang/lib/Testing/CommandLineArgs.cpp
clang/unittests/Tooling/CMakeLists.txt
clang/unittests/Tooling/FixItTest.cpp
clang/unittests/Tooling/StandardLibraryTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h
index e5577e74fa639..dc1a0efe1c475 100644
--- a/clang/include/clang/Basic/Diagnostic.h
+++ b/clang/include/clang/Basic/Diagnostic.h
@@ -39,7 +39,8 @@
namespace llvm {
class Error;
-}
+class raw_ostream;
+} // namespace llvm
namespace clang {
@@ -1717,6 +1718,9 @@ class StoredDiagnostic {
}
};
+// Simple debug printing of StoredDiagnostic.
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const StoredDiagnostic &);
+
/// Abstract interface, implemented by clients of the front-end, which
/// formats and prints fully processed diagnostics.
class DiagnosticConsumer {
diff --git a/clang/include/clang/Testing/CommandLineArgs.h b/clang/include/clang/Testing/CommandLineArgs.h
index 95979a2bfb80e..fe2103a3dce21 100644
--- a/clang/include/clang/Testing/CommandLineArgs.h
+++ b/clang/include/clang/Testing/CommandLineArgs.h
@@ -33,6 +33,7 @@ enum TestLanguage {
};
std::vector<std::string> getCommandLineArgsForTesting(TestLanguage Lang);
+std::vector<std::string> getCC1ArgsForTesting(TestLanguage Lang);
StringRef getFilenameForTesting(TestLanguage Lang);
diff --git a/clang/include/clang/Testing/TestAST.h b/clang/include/clang/Testing/TestAST.h
new file mode 100644
index 0000000000000..b1ccf459e4b3b
--- /dev/null
+++ b/clang/include/clang/Testing/TestAST.h
@@ -0,0 +1,91 @@
+//===--- TestAST.h - Build clang ASTs for testing -------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// In normal operation of Clang, the FrontendAction's lifecycle both creates
+// and destroys the AST, and code should operate on it during callbacks in
+// between (e.g. via ASTConsumer).
+//
+// For tests it is often more convenient to parse an AST from code, and keep it
+// alive as a normal local object, with assertions as straight-line code.
+// TestAST provides such an interface.
+// (ASTUnit can be used for this purpose, but is a production library with
+// broad scope and complicated API).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TESTING_TESTAST_H
+#define LLVM_CLANG_TESTING_TESTAST_H
+
+#include "clang/Basic/LLVM.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Testing/CommandLineArgs.h"
+#include "llvm/ADT/StringRef.h"
+#include <string>
+#include <vector>
+
+namespace clang {
+
+/// Specifies a virtual source file to be parsed as part of a test.
+struct TestInputs {
+ TestInputs() = default;
+ TestInputs(StringRef Code) : Code(Code) {}
+
+ /// The source code of the input file to be parsed.
+ std::string Code;
+
+ /// The language to parse as.
+ /// This affects the -x and -std flags used, and the filename.
+ TestLanguage Language = TestLanguage::Lang_OBJCXX;
+
+ /// Extra argv to pass to clang -cc1.
+ std::vector<std::string> ExtraArgs = {};
+
+ /// By default, error diagnostics during parsing are reported as gtest errors.
+ /// To suppress this, set ErrorOK or include "error-ok" in a comment in Code.
+ /// In either case, all diagnostics appear in TestAST::diagnostics().
+ bool ErrorOK = false;
+};
+
+/// The result of parsing a file specified by TestInputs.
+///
+/// The ASTContext, Sema etc are valid as long as this object is alive.
+class TestAST {
+public:
+ /// Constructing a TestAST parses the virtual file.
+ ///
+ /// To keep tests terse, critical errors (e.g. invalid flags) are reported as
+ /// unit test failures with ADD_FAILURE() and produce an empty ASTContext,
+ /// Sema etc. This frees the test code from handling these explicitly.
+ TestAST(const TestInputs &);
+ TestAST(StringRef Code) : TestAST(TestInputs(Code)) {}
+ TestAST(TestAST &&M);
+ TestAST &operator=(TestAST &&);
+ ~TestAST();
+
+ /// Provides access to the AST context and other parts of Clang.
+
+ ASTContext &context() { return Clang->getASTContext(); }
+ Sema &sema() { return Clang->getSema(); }
+ SourceManager &sourceManager() { return Clang->getSourceManager(); }
+ FileManager &fileManager() { return Clang->getFileManager(); }
+ Preprocessor &preprocessor() { return Clang->getPreprocessor(); }
+
+ /// Returns diagnostics emitted during parsing.
+ /// (By default, errors cause test failures, see TestInputs::ErrorOK).
+ llvm::ArrayRef<StoredDiagnostic> diagnostics() { return Diagnostics; }
+
+private:
+ void clear();
+ std::unique_ptr<FrontendAction> Action;
+ std::unique_ptr<CompilerInstance> Clang;
+ std::vector<StoredDiagnostic> Diagnostics;
+};
+
+} // end namespace clang
+
+#endif
diff --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp
index ac4b9d2cd5a2b..3315012685d69 100644
--- a/clang/lib/Basic/Diagnostic.cpp
+++ b/clang/lib/Basic/Diagnostic.cpp
@@ -1138,6 +1138,14 @@ StoredDiagnostic::StoredDiagnostic(DiagnosticsEngine::Level Level, unsigned ID,
{
}
+llvm::raw_ostream &clang::operator<<(llvm::raw_ostream &OS,
+ const StoredDiagnostic &SD) {
+ if (SD.getLocation().hasManager())
+ OS << SD.getLocation().printToString(SD.getLocation().getManager()) << ": ";
+ OS << SD.getMessage();
+ return OS;
+}
+
/// IncludeInDiagnosticCounts - This method (whose default implementation
/// returns true) indicates whether the diagnostics handled by this
/// DiagnosticConsumer should be included in the number of diagnostics
diff --git a/clang/lib/Testing/CMakeLists.txt b/clang/lib/Testing/CMakeLists.txt
index dbaba54bb8cab..eb0aea5fca027 100644
--- a/clang/lib/Testing/CMakeLists.txt
+++ b/clang/lib/Testing/CMakeLists.txt
@@ -1,14 +1,19 @@
-set(LLVM_LINK_COMPONENTS
- Support
- )
-
# Not add_clang_library: this is not part of clang's public library interface.
# Unit tests should depend on this with target_link_libraries(), rather
# than with clang_target_link_libraries().
add_llvm_library(clangTesting
CommandLineArgs.cpp
+ TestAST.cpp
+
BUILDTREE_ONLY
LINK_COMPONENTS
Support
)
+
+target_link_libraries(clangTesting
+ PRIVATE
+ llvm_gtest
+ clangBasic
+ clangFrontend
+ )
diff --git a/clang/lib/Testing/CommandLineArgs.cpp b/clang/lib/Testing/CommandLineArgs.cpp
index cd4d8c188da90..d71b9361f9589 100644
--- a/clang/lib/Testing/CommandLineArgs.cpp
+++ b/clang/lib/Testing/CommandLineArgs.cpp
@@ -45,6 +45,39 @@ std::vector<std::string> getCommandLineArgsForTesting(TestLanguage Lang) {
return Args;
}
+std::vector<std::string> getCC1ArgsForTesting(TestLanguage Lang) {
+ std::vector<std::string> Args;
+ switch (Lang) {
+ case Lang_C89:
+ Args = {"-xc", "-std=c89"};
+ break;
+ case Lang_C99:
+ Args = {"-xc", "-std=c99"};
+ break;
+ case Lang_CXX03:
+ Args = {"-std=c++03"};
+ break;
+ case Lang_CXX11:
+ Args = {"-std=c++11"};
+ break;
+ case Lang_CXX14:
+ Args = {"-std=c++14"};
+ break;
+ case Lang_CXX17:
+ Args = {"-std=c++17"};
+ break;
+ case Lang_CXX20:
+ Args = {"-std=c++20"};
+ break;
+ case Lang_OBJCXX:
+ Args = {"-xobjective-c++"};
+ break;
+ case Lang_OpenCL:
+ llvm_unreachable("Not implemented yet!");
+ }
+ return Args;
+}
+
StringRef getFilenameForTesting(TestLanguage Lang) {
switch (Lang) {
case Lang_C89:
diff --git a/clang/lib/Testing/TestAST.cpp b/clang/lib/Testing/TestAST.cpp
new file mode 100644
index 0000000000000..689d407b25537
--- /dev/null
+++ b/clang/lib/Testing/TestAST.cpp
@@ -0,0 +1,158 @@
+//===--- TestAST.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 "clang/Testing/TestAST.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Frontend/TextDiagnostic.h"
+#include "clang/Testing/CommandLineArgs.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace {
+
+// Captures diagnostics into a vector, optionally reporting errors to gtest.
+class StoreDiagnostics : public DiagnosticConsumer {
+ std::vector<StoredDiagnostic> &Out;
+ bool ReportErrors;
+ LangOptions LangOpts;
+
+public:
+ StoreDiagnostics(std::vector<StoredDiagnostic> &Out, bool ReportErrors)
+ : Out(Out), ReportErrors(ReportErrors) {}
+
+ void BeginSourceFile(const LangOptions &LangOpts,
+ const Preprocessor *) override {
+ this->LangOpts = LangOpts;
+ }
+
+ void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
+ const Diagnostic &Info) override {
+ Out.emplace_back(DiagLevel, Info);
+ if (ReportErrors && DiagLevel >= DiagnosticsEngine::Error) {
+ std::string Text;
+ llvm::raw_string_ostream OS(Text);
+ TextDiagnostic Renderer(OS, LangOpts,
+ &Info.getDiags()->getDiagnosticOptions());
+ Renderer.emitStoredDiagnostic(Out.back());
+ ADD_FAILURE() << Text;
+ }
+ }
+};
+
+// Fills in the bits of a CompilerInstance that weren't initialized yet.
+// Provides "empty" ASTContext etc if we fail before parsing gets started.
+void createMissingComponents(CompilerInstance &Clang) {
+ if (!Clang.hasDiagnostics())
+ Clang.createDiagnostics();
+ if (!Clang.hasFileManager())
+ Clang.createFileManager();
+ if (!Clang.hasSourceManager())
+ Clang.createSourceManager(Clang.getFileManager());
+ if (!Clang.hasTarget())
+ Clang.createTarget();
+ if (!Clang.hasPreprocessor())
+ Clang.createPreprocessor(TU_Complete);
+ if (!Clang.hasASTConsumer())
+ Clang.setASTConsumer(std::make_unique<ASTConsumer>());
+ if (!Clang.hasASTContext())
+ Clang.createASTContext();
+ if (!Clang.hasSema())
+ Clang.createSema(TU_Complete, /*CodeCompleteConsumer=*/nullptr);
+}
+
+} // namespace
+
+TestAST::TestAST(const TestInputs &In) {
+ Clang = std::make_unique<CompilerInstance>(
+ std::make_shared<PCHContainerOperations>());
+ // If we don't manage to finish parsing, create CompilerInstance components
+ // anyway so that the test will see an empty AST instead of crashing.
+ auto RecoverFromEarlyExit =
+ llvm::make_scope_exit([&] { createMissingComponents(*Clang); });
+
+ // Extra error conditions are reported through diagnostics, set that up first.
+ bool ErrorOK = In.ErrorOK || llvm::StringRef(In.Code).contains("error-ok");
+ Clang->createDiagnostics(new StoreDiagnostics(Diagnostics, !ErrorOK));
+
+ // Parse cc1 argv, (typically [-std=c++20 input.cc]) into CompilerInvocation.
+ std::vector<const char *> Argv;
+ std::vector<std::string> LangArgs = getCC1ArgsForTesting(In.Language);
+ for (const auto &S : LangArgs)
+ Argv.push_back(S.c_str());
+ for (const auto &S : In.ExtraArgs)
+ Argv.push_back(S.c_str());
+ std::string Filename = getFilenameForTesting(In.Language).str();
+ Argv.push_back(Filename.c_str());
+ Clang->setInvocation(std::make_unique<CompilerInvocation>());
+ if (!CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv,
+ Clang->getDiagnostics(), "clang")) {
+ ADD_FAILURE() << "Failed to create invocation";
+ return;
+ }
+ assert(!Clang->getInvocation().getFrontendOpts().DisableFree);
+
+ // Set up a VFS with only the virtual file visible.
+ auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+ VFS->addFile(Filename, /*ModificationTime=*/0,
+ llvm::MemoryBuffer::getMemBufferCopy(In.Code, Filename));
+ Clang->createFileManager(VFS);
+
+ // Running the FrontendAction creates the other components: SourceManager,
+ // Preprocessor, ASTContext, Sema. Preprocessor needs TargetInfo to be set.
+ EXPECT_TRUE(Clang->createTarget());
+ Action = std::make_unique<SyntaxOnlyAction>();
+ const FrontendInputFile &Main = Clang->getFrontendOpts().Inputs.front();
+ if (!Action->BeginSourceFile(*Clang, Main)) {
+ ADD_FAILURE() << "Failed to BeginSourceFile()";
+ Action.reset(); // Don't call EndSourceFile if BeginSourceFile failed.
+ return;
+ }
+ if (auto Err = Action->Execute())
+ ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err));
+
+ // Action->EndSourceFile() would destroy the ASTContext, we want to keep it.
+ // But notify the preprocessor we're done now.
+ Clang->getPreprocessor().EndSourceFile();
+ // We're done gathering diagnostics, detach the consumer so we can destroy it.
+ Clang->getDiagnosticClient().EndSourceFile();
+ Clang->getDiagnostics().setClient(new DiagnosticConsumer(),
+ /*ShouldOwnClient=*/true);
+}
+
+void TestAST::clear() {
+ if (Action) {
+ // We notified the preprocessor of EOF already, so detach it first.
+ // Sema needs the PP alive until after EndSourceFile() though.
+ auto PP = Clang->getPreprocessorPtr(); // Keep PP alive for now.
+ Clang->setPreprocessor(nullptr); // Detach so we don't send EOF twice.
+ Action->EndSourceFile(); // Destroy ASTContext and Sema.
+ // Now Sema is gone, PP can safely be destroyed.
+ }
+ Action.reset();
+ Clang.reset();
+ Diagnostics.clear();
+}
+
+TestAST &TestAST::operator=(TestAST &&M) {
+ clear();
+ Action = std::move(M.Action);
+ Clang = std::move(M.Clang);
+ Diagnostics = std::move(M.Diagnostics);
+ return *this;
+}
+
+TestAST::TestAST(TestAST &&M) { *this = std::move(M); }
+
+TestAST::~TestAST() { clear(); }
+
+} // end namespace clang
diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt
index b532e44cea668..dfc1f59cf0a83 100644
--- a/clang/unittests/Tooling/CMakeLists.txt
+++ b/clang/unittests/Tooling/CMakeLists.txt
@@ -88,6 +88,7 @@ clang_target_link_libraries(ToolingTests
target_link_libraries(ToolingTests
PRIVATE
LLVMTestingSupport
+ clangTesting
)
add_subdirectory(Syntax)
diff --git a/clang/unittests/Tooling/FixItTest.cpp b/clang/unittests/Tooling/FixItTest.cpp
index ec9801d3455ca..f556c249bb888 100644
--- a/clang/unittests/Tooling/FixItTest.cpp
+++ b/clang/unittests/Tooling/FixItTest.cpp
@@ -6,9 +6,11 @@
//
//===----------------------------------------------------------------------===//
-#include "TestVisitor.h"
-#include "clang/Basic/Diagnostic.h"
#include "clang/Tooling/FixIt.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Testing/TestAST.h"
+#include "gtest/gtest.h"
using namespace clang;
@@ -18,214 +20,169 @@ using tooling::fixit::createReplacement;
namespace {
-struct CallsVisitor : TestVisitor<CallsVisitor> {
- bool VisitCallExpr(CallExpr *Expr) {
- OnCall(Expr, Context);
- return true;
- }
-
- std::function<void(CallExpr *, ASTContext *Context)> OnCall;
-};
-
-std::string LocationToString(SourceLocation Loc, ASTContext *Context) {
- return Loc.printToString(Context->getSourceManager());
+const CallExpr &onlyCall(ASTContext &Ctx) {
+ using namespace ast_matchers;
+ auto Calls = match(callExpr().bind(""), Ctx);
+ EXPECT_EQ(Calls.size(), 1u);
+ return *Calls.front().getNodeAs<CallExpr>("");
}
TEST(FixItTest, getText) {
- CallsVisitor Visitor;
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- EXPECT_EQ("foo(x, y)", getText(*CE, *Context));
- EXPECT_EQ("foo(x, y)", getText(CE->getSourceRange(), *Context));
-
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- EXPECT_EQ("x", getText(*P0, *Context));
- EXPECT_EQ("y", getText(*P1, *Context));
- };
- Visitor.runOver("void foo(int x, int y) { foo(x, y); }");
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- EXPECT_EQ("APPLY(foo, x, y)", getText(*CE, *Context));
- };
- Visitor.runOver("#define APPLY(f, x, y) f(x, y)\n"
- "void foo(int x, int y) { APPLY(foo, x, y); }");
+ TestAST AST("void foo(int x, int y) { foo(x, y); }");
+ const CallExpr &CE = onlyCall(AST.context());
+ EXPECT_EQ("foo(x, y)", getText(CE, AST.context()));
+ EXPECT_EQ("foo(x, y)", getText(CE.getSourceRange(), AST.context()));
+ EXPECT_EQ("x", getText(*CE.getArg(0), AST.context()));
+ EXPECT_EQ("y", getText(*CE.getArg(1), AST.context()));
+
+ AST = TestAST("#define APPLY(f, x, y) f(x, y)\n"
+ "void foo(int x, int y) { APPLY(foo, x, y); }");
+ const CallExpr &CE2 = onlyCall(AST.context());
+ EXPECT_EQ("APPLY(foo, x, y)", getText(CE2, AST.context()));
}
TEST(FixItTest, getTextWithMacro) {
- CallsVisitor Visitor;
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- EXPECT_EQ("F OO", getText(*CE, *Context));
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- EXPECT_EQ("", getText(*P0, *Context));
- EXPECT_EQ("", getText(*P1, *Context));
- };
- Visitor.runOver("#define F foo(\n"
- "#define OO x, y)\n"
- "void foo(int x, int y) { F OO ; }");
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- EXPECT_EQ("", getText(*CE, *Context));
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- EXPECT_EQ("x", getText(*P0, *Context));
- EXPECT_EQ("y", getText(*P1, *Context));
- };
- Visitor.runOver("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n"
- "void foo(int x, int y) { FOO(x,y) }");
+ TestAST AST("#define F foo(\n"
+ "#define OO x, y)\n"
+ "void foo(int x, int y) { F OO ; }");
+ const CallExpr &CE = onlyCall(AST.context());
+ EXPECT_EQ("F OO", getText(CE, AST.context()));
+ EXPECT_EQ("", getText(*CE.getArg(0), AST.context()));
+ EXPECT_EQ("", getText(*CE.getArg(1), AST.context()));
+
+ AST = TestAST("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n"
+ "void foo(int x, int y) { FOO(x,y) }");
+ const CallExpr &CE2 = onlyCall(AST.context());
+ EXPECT_EQ("", getText(CE2, AST.context()));
+ EXPECT_EQ("x", getText(*CE2.getArg(0), AST.context()));
+ EXPECT_EQ("y", getText(*CE2.getArg(1), AST.context()));
}
TEST(FixItTest, createRemoval) {
- CallsVisitor Visitor;
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- FixItHint Hint = createRemoval(*CE);
- EXPECT_EQ("foo(x, y)", getText(Hint.RemoveRange.getAsRange(), *Context));
- EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint.CodeToInsert.empty());
-
- Expr *P0 = CE->getArg(0);
- FixItHint Hint0 = createRemoval(*P0);
- EXPECT_EQ("x", getText(Hint0.RemoveRange.getAsRange(), *Context));
- EXPECT_TRUE(Hint0.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint0.CodeToInsert.empty());
-
- Expr *P1 = CE->getArg(1);
- FixItHint Hint1 = createRemoval(*P1);
- EXPECT_EQ("y", getText(Hint1.RemoveRange.getAsRange(), *Context));
- EXPECT_TRUE(Hint1.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint1.CodeToInsert.empty());
- };
- Visitor.runOver("void foo(int x, int y) { foo(x, y); }");
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- Expr *P0 = CE->getArg(0);
- FixItHint Hint0 = createRemoval(*P0);
- EXPECT_EQ("x + y", getText(Hint0.RemoveRange.getAsRange(), *Context));
-
- Expr *P1 = CE->getArg(1);
- FixItHint Hint1 = createRemoval(*P1);
- EXPECT_EQ("y + x", getText(Hint1.RemoveRange.getAsRange(), *Context));
- };
- Visitor.runOver("void foo(int x, int y) { foo(x + y, y + x); }");
+ TestAST AST("void foo(int x, int y) { foo(x, y); }");
+ const CallExpr &CE = onlyCall(AST.context());
+
+ FixItHint Hint = createRemoval(CE);
+ EXPECT_EQ("foo(x, y)", getText(Hint.RemoveRange.getAsRange(), AST.context()));
+ EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint.CodeToInsert.empty());
+
+ FixItHint Hint0 = createRemoval(*CE.getArg(0));
+ EXPECT_EQ("x", getText(Hint0.RemoveRange.getAsRange(), AST.context()));
+ EXPECT_TRUE(Hint0.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint0.CodeToInsert.empty());
+
+ FixItHint Hint1 = createRemoval(*CE.getArg(1));
+ EXPECT_EQ("y", getText(Hint1.RemoveRange.getAsRange(), AST.context()));
+ EXPECT_TRUE(Hint1.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint1.CodeToInsert.empty());
+
+ AST = TestAST("void foo(int x, int y) { foo(x + y, y + x); }");
+ const CallExpr &CE2 = onlyCall(AST.context());
+ Hint0 = createRemoval(*CE2.getArg(0));
+ EXPECT_EQ("x + y", getText(Hint0.RemoveRange.getAsRange(), AST.context()));
+
+ Hint1 = createRemoval(*CE2.getArg(1));
+ EXPECT_EQ("y + x", getText(Hint1.RemoveRange.getAsRange(), AST.context()));
}
TEST(FixItTest, createRemovalWithMacro) {
- CallsVisitor Visitor;
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- FixItHint Hint = createRemoval(*CE);
- EXPECT_EQ("FOO", getText(Hint.RemoveRange.getAsRange(), *Context));
- EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint.CodeToInsert.empty());
-
- Expr *P0 = CE->getArg(0);
- FixItHint Hint0 = createRemoval(*P0);
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:17>",
- LocationToString(Hint0.RemoveRange.getBegin(), Context));
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:17>",
- LocationToString(Hint0.RemoveRange.getEnd(), Context));
- EXPECT_TRUE(Hint0.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint0.CodeToInsert.empty());
-
- Expr *P1 = CE->getArg(1);
- FixItHint Hint1 = createRemoval(*P1);
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:20>",
- LocationToString(Hint1.RemoveRange.getBegin(), Context));
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:20>",
- LocationToString(Hint1.RemoveRange.getEnd(), Context));
- EXPECT_TRUE(Hint1.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint1.CodeToInsert.empty());
- };
- Visitor.runOver("#define FOO foo(1, 1)\n"
- "void foo(int x, int y) { FOO; }");
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- FixItHint Hint = createRemoval(*CE);
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:37>",
- LocationToString(Hint.RemoveRange.getBegin(), Context));
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:45>",
- LocationToString(Hint.RemoveRange.getEnd(), Context));
- EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint.CodeToInsert.empty());
- };
- Visitor.runOver("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n"
- "void foo(int x, int y) { FOO(x,y) }");
+ TestAST AST("#define FOO foo(1, 1)\n"
+ "void foo(int x, int y) { FOO; }");
+ const CallExpr &CE = onlyCall(AST.context());
+ FixItHint Hint = createRemoval(CE);
+ EXPECT_EQ("FOO", getText(Hint.RemoveRange.getAsRange(), AST.context()));
+ EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint.CodeToInsert.empty());
+
+ FixItHint Hint0 = createRemoval(*CE.getArg(0));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>",
+ Hint0.RemoveRange.getBegin().printToString(AST.sourceManager()));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>",
+ Hint0.RemoveRange.getEnd().printToString(AST.sourceManager()));
+ EXPECT_TRUE(Hint0.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint0.CodeToInsert.empty());
+
+ FixItHint Hint1 = createRemoval(*CE.getArg(1));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:20>",
+ Hint1.RemoveRange.getBegin().printToString(AST.sourceManager()));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:20>",
+ Hint1.RemoveRange.getEnd().printToString(AST.sourceManager()));
+ EXPECT_TRUE(Hint1.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint1.CodeToInsert.empty());
+
+ AST = TestAST("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n"
+ "void foo(int x, int y) { FOO(x,y) }");
+ const CallExpr &CE2 = onlyCall(AST.context());
+ Hint = createRemoval(CE2);
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:37>",
+ Hint.RemoveRange.getBegin().printToString(AST.sourceManager()));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:45>",
+ Hint.RemoveRange.getEnd().printToString(AST.sourceManager()));
+ EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint.CodeToInsert.empty());
}
TEST(FixItTest, createReplacement) {
- CallsVisitor Visitor;
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- FixItHint Hint0 = createReplacement(*P0, *P1, *Context);
- FixItHint Hint1 = createReplacement(*P1, *P0, *Context);
+ for (const char *Code : {
+ "void foo(int x, int y) { foo(x, y); }",
+
+ "#define APPLY(f, x, y) f(x, y)\n"
+ "void foo(int x, int y) { APPLY(foo, x, y); }",
+
+ "#define APPLY(f, P) f(P)\n"
+ "#define PAIR(x, y) x, y\n"
+ "void foo(int x, int y) { APPLY(foo, PAIR(x, y)); }\n",
+ }) {
+ TestAST AST(Code);
+ const CallExpr &CE = onlyCall(AST.context());
+ const Expr *P0 = CE.getArg(0);
+ const Expr *P1 = CE.getArg(1);
+ FixItHint Hint0 = createReplacement(*P0, *P1, AST.context());
+ FixItHint Hint1 = createReplacement(*P1, *P0, AST.context());
// Validate Hint0 fields.
- EXPECT_EQ("x", getText(Hint0.RemoveRange.getAsRange(), *Context));
+ EXPECT_EQ("x", getText(Hint0.RemoveRange.getAsRange(), AST.context()));
EXPECT_TRUE(Hint0.InsertFromRange.isInvalid());
EXPECT_EQ(Hint0.CodeToInsert, "y");
// Validate Hint1 fields.
- EXPECT_EQ("y", getText(Hint1.RemoveRange.getAsRange(), *Context));
+ EXPECT_EQ("y", getText(Hint1.RemoveRange.getAsRange(), AST.context()));
EXPECT_TRUE(Hint1.InsertFromRange.isInvalid());
EXPECT_EQ(Hint1.CodeToInsert, "x");
- };
-
- Visitor.runOver("void foo(int x, int y) { foo(x, y); }");
-
- Visitor.runOver("#define APPLY(f, x, y) f(x, y)\n"
- "void foo(int x, int y) { APPLY(foo, x, y); }");
-
- Visitor.runOver("#define APPLY(f, P) f(P)\n"
- "#define PAIR(x, y) x, y\n"
- "void foo(int x, int y) { APPLY(foo, PAIR(x, y)); }\n");
+ }
}
TEST(FixItTest, createReplacementWithMacro) {
- CallsVisitor Visitor;
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- FixItHint Hint = createReplacement(*P0, *P1, *Context);
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:17>",
- LocationToString(Hint.RemoveRange.getBegin(), Context));
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:1:17>",
- LocationToString(Hint.RemoveRange.getEnd(), Context));
- EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
- EXPECT_TRUE(Hint.CodeToInsert.empty());
- };
-
- Visitor.runOver("#define FOO foo(1, 1)\n"
- "void foo(int x, int y) { FOO; }");
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- FixItHint Hint = createReplacement(*P0, *P1, *Context);
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:2:30>",
- LocationToString(Hint.RemoveRange.getBegin(), Context));
- EXPECT_EQ("input.cc:2:26 <Spelling=input.cc:2:30>",
- LocationToString(Hint.RemoveRange.getEnd(), Context));
- EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
- EXPECT_EQ("y", Hint.CodeToInsert);
- };
- Visitor.runOver("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n"
- "void foo(int x, int y) { FOO(x,y) }");
-
- Visitor.OnCall = [](CallExpr *CE, ASTContext *Context) {
- Expr *P0 = CE->getArg(0);
- Expr *P1 = CE->getArg(1);
- FixItHint Hint = createReplacement(*P0, *P1, *Context);
- EXPECT_EQ("x + y", getText(Hint.RemoveRange.getAsRange(), *Context));
- EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
- EXPECT_EQ("y + x", Hint.CodeToInsert);
- };
- Visitor.runOver("void foo(int x, int y) { foo(x + y, y + x); }");
+ TestAST AST("#define FOO foo(1, 1)\n"
+ "void foo(int x, int y) { FOO; }");
+ const CallExpr &CE = onlyCall(AST.context());
+ FixItHint Hint =
+ createReplacement(*CE.getArg(0), *CE.getArg(1), AST.context());
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>",
+ Hint.RemoveRange.getBegin().printToString(AST.sourceManager()));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>",
+ Hint.RemoveRange.getEnd().printToString(AST.sourceManager()));
+ EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
+ EXPECT_TRUE(Hint.CodeToInsert.empty());
+
+ AST = TestAST("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n"
+ "void foo(int x, int y) { FOO(x,y) }");
+ const CallExpr &CE2 = onlyCall(AST.context());
+ Hint = createReplacement(*CE2.getArg(0), *CE2.getArg(1), AST.context());
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:2:30>",
+ Hint.RemoveRange.getEnd().printToString(AST.sourceManager()));
+ EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:2:30>",
+ Hint.RemoveRange.getBegin().printToString(AST.sourceManager()));
+ EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
+ EXPECT_EQ("y", Hint.CodeToInsert);
+
+ AST = TestAST("void foo(int x, int y) { foo(x + y, y + x); }");
+ const CallExpr &CE3 = onlyCall(AST.context());
+ Hint = createReplacement(*CE3.getArg(0), *CE3.getArg(1), AST.context());
+ EXPECT_EQ("x + y", getText(Hint.RemoveRange.getAsRange(), AST.context()));
+ EXPECT_TRUE(Hint.InsertFromRange.isInvalid());
+ EXPECT_EQ("y + x", Hint.CodeToInsert);
}
} // end anonymous namespace
diff --git a/clang/unittests/Tooling/StandardLibraryTest.cpp b/clang/unittests/Tooling/StandardLibraryTest.cpp
index 617104c37c510..64c01b56eab88 100644
--- a/clang/unittests/Tooling/StandardLibraryTest.cpp
+++ b/clang/unittests/Tooling/StandardLibraryTest.cpp
@@ -7,10 +7,10 @@
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Inclusions/StandardLibrary.h"
+#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclarationName.h"
-#include "clang/Frontend/ASTUnit.h"
-#include "clang/Tooling/Tooling.h"
+#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ScopedPrinter.h"
@@ -24,10 +24,9 @@ namespace clang {
namespace tooling {
namespace {
-const NamedDecl &lookup(ASTUnit &AST, llvm::StringRef Name) {
- auto &Ctx = AST.getASTContext();
- TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl();
- auto Result = TU->lookup(DeclarationName(&Ctx.Idents.get(Name)));
+const NamedDecl &lookup(TestAST &AST, llvm::StringRef Name) {
+ TranslationUnitDecl *TU = AST.context().getTranslationUnitDecl();
+ auto Result = TU->lookup(DeclarationName(&AST.context().Idents.get(Name)));
assert(!Result.empty() && "Lookup failed");
assert(Result.isSingleResult() && "Lookup returned multiple results");
return *Result.front();
@@ -50,7 +49,7 @@ TEST(StdlibTest, All) {
}
TEST(StdlibTest, Recognizer) {
- std::unique_ptr<ASTUnit> AST = buildASTFromCode(R"cpp(
+ TestAST AST(R"cpp(
namespace std {
inline namespace inl {
@@ -83,17 +82,15 @@ TEST(StdlibTest, Recognizer) {
div_t div;
)cpp");
- auto &VectorNonstd = lookup(*AST, "vector");
- auto *Vec =
- cast<VarDecl>(lookup(*AST, "vec")).getType()->getAsCXXRecordDecl();
+ auto &VectorNonstd = lookup(AST, "vector");
+ auto *Vec = cast<VarDecl>(lookup(AST, "vec")).getType()->getAsCXXRecordDecl();
auto *Nest =
- cast<VarDecl>(lookup(*AST, "nest")).getType()->getAsCXXRecordDecl();
+ cast<VarDecl>(lookup(AST, "nest")).getType()->getAsCXXRecordDecl();
auto *Clock =
- cast<VarDecl>(lookup(*AST, "clock")).getType()->getAsCXXRecordDecl();
- auto *Sec =
- cast<VarDecl>(lookup(*AST, "sec")).getType()->getAsCXXRecordDecl();
+ cast<VarDecl>(lookup(AST, "clock")).getType()->getAsCXXRecordDecl();
+ auto *Sec = cast<VarDecl>(lookup(AST, "sec")).getType()->getAsCXXRecordDecl();
auto *CDivT =
- cast<VarDecl>(lookup(*AST, "div")).getType()->getAsCXXRecordDecl();
+ cast<VarDecl>(lookup(AST, "div")).getType()->getAsCXXRecordDecl();
stdlib::Recognizer Recognizer;
More information about the cfe-commits
mailing list