diff options
Diffstat (limited to 'clang/unittests/Tooling/Syntax/TreeTest.cpp')
-rw-r--r-- | clang/unittests/Tooling/Syntax/TreeTest.cpp | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/clang/unittests/Tooling/Syntax/TreeTest.cpp b/clang/unittests/Tooling/Syntax/TreeTest.cpp new file mode 100644 index 00000000000..5ce00de3062 --- /dev/null +++ b/clang/unittests/Tooling/Syntax/TreeTest.cpp @@ -0,0 +1,160 @@ +//===- TreeTest.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/Tooling/Syntax/Tree.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/Decl.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Tooling/Syntax/BuildTree.h" +#include "clang/Tooling/Syntax/Nodes.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <cstdlib> + +using namespace clang; + +namespace { +class SyntaxTreeTest : public ::testing::Test { +protected: + // Build a syntax tree for the code. + syntax::TranslationUnit *buildTree(llvm::StringRef Code) { + // FIXME: this code is almost the identical to the one in TokensTest. Share + // it. + class BuildSyntaxTree : public ASTConsumer { + public: + BuildSyntaxTree(syntax::TranslationUnit *&Root, + std::unique_ptr<syntax::Arena> &Arena, + std::unique_ptr<syntax::TokenCollector> Tokens) + : Root(Root), Arena(Arena), Tokens(std::move(Tokens)) { + assert(this->Tokens); + } + + void HandleTranslationUnit(ASTContext &Ctx) override { + Arena = llvm::make_unique<syntax::Arena>(Ctx.getSourceManager(), + Ctx.getLangOpts(), + std::move(*Tokens).consume()); + Tokens = nullptr; // make sure we fail if this gets called twice. + Root = syntax::buildSyntaxTree(*Arena, *Ctx.getTranslationUnitDecl()); + } + + private: + syntax::TranslationUnit *&Root; + std::unique_ptr<syntax::Arena> &Arena; + std::unique_ptr<syntax::TokenCollector> Tokens; + }; + + class BuildSyntaxTreeAction : public ASTFrontendAction { + public: + BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, + std::unique_ptr<syntax::Arena> &Arena) + : Root(Root), Arena(Arena) {} + + std::unique_ptr<ASTConsumer> + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + // We start recording the tokens, ast consumer will take on the result. + auto Tokens = + llvm::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); + return llvm::make_unique<BuildSyntaxTree>(Root, Arena, + std::move(Tokens)); + } + + private: + syntax::TranslationUnit *&Root; + std::unique_ptr<syntax::Arena> &Arena; + }; + + constexpr const char *FileName = "./input.cpp"; + FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); + if (!Diags->getClient()) + Diags->setClient(new IgnoringDiagConsumer); + // Prepare to run a compiler. + std::vector<const char *> Args = {"syntax-test", "-std=c++11", + "-fsyntax-only", FileName}; + auto CI = createInvocationFromCommandLine(Args, Diags, FS); + assert(CI); + CI->getFrontendOpts().DisableFree = false; + CI->getPreprocessorOpts().addRemappedFile( + FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); + CompilerInstance Compiler; + Compiler.setInvocation(std::move(CI)); + Compiler.setDiagnostics(Diags.get()); + Compiler.setFileManager(FileMgr.get()); + Compiler.setSourceManager(SourceMgr.get()); + + syntax::TranslationUnit *Root = nullptr; + BuildSyntaxTreeAction Recorder(Root, this->Arena); + if (!Compiler.ExecuteAction(Recorder)) { + ADD_FAILURE() << "failed to run the frontend"; + std::abort(); + } + return Root; + } + + // Adds a file to the test VFS. + void addFile(llvm::StringRef Path, llvm::StringRef Contents) { + if (!FS->addFile(Path, time_t(), + llvm::MemoryBuffer::getMemBufferCopy(Contents))) { + ADD_FAILURE() << "could not add a file to VFS: " << Path; + } + } + + // Data fields. + llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags = + new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions); + IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS = + new llvm::vfs::InMemoryFileSystem; + llvm::IntrusiveRefCntPtr<FileManager> FileMgr = + new FileManager(FileSystemOptions(), FS); + llvm::IntrusiveRefCntPtr<SourceManager> SourceMgr = + new SourceManager(*Diags, *FileMgr); + // Set after calling buildTree(). + std::unique_ptr<syntax::Arena> Arena; +}; + +TEST_F(SyntaxTreeTest, Basic) { + std::pair</*Input*/ std::string, /*Expected*/ std::string> Cases[] = { + { + R"cpp( +int main() {} +void foo() {} + )cpp", + R"txt( +*: TranslationUnit +|-TopLevelDeclaration +| |-int +| |-main +| |-( +| |-) +| `-CompoundStatement +| |-1: { +| `-2: } +|-TopLevelDeclaration +| |-void +| |-foo +| |-( +| |-) +| `-CompoundStatement +| |-1: { +| `-2: } +`-<eof> +)txt"}, + }; + + for (const auto &T : Cases) { + auto *Root = buildTree(T.first); + std::string Expected = llvm::StringRef(T.second).trim().str(); + std::string Actual = llvm::StringRef(Root->dump(*Arena)).trim(); + EXPECT_EQ(Expected, Actual) << "the resulting dump is:\n" << Actual; + } +} +} // namespace |