summaryrefslogtreecommitdiffstats
path: root/unittests
diff options
context:
space:
mode:
authorManuel Klimek <klimek@google.com>2012-04-04 12:07:46 +0000
committerManuel Klimek <klimek@google.com>2012-04-04 12:07:46 +0000
commitcb971c6726d16e12ecd2a340941d7f5c06698332 (patch)
tree63f54a02c06faaf2297917a6e475852f1e959747 /unittests
parentc9aa9c00fc99ded37a064d607b71815484e20652 (diff)
Adds a tooling library.
Provides an API to run clang tools (FrontendActions) as standalone tools, or repeatedly in-memory in a process. This is useful for unit-testing, map-reduce style applications, source transformation daemons or command line tools. The ability to run over multiple translation units with different command line arguments enables building up refactoring tools that need to apply transformations across translation unit boundaries. See tools/clang-check/ClangCheck.cpp for an example. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@154008 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'unittests')
-rw-r--r--unittests/CMakeLists.txt6
-rw-r--r--unittests/Makefile2
-rw-r--r--unittests/Tooling/CompilationDatabaseTest.cpp223
-rw-r--r--unittests/Tooling/Makefile17
-rw-r--r--unittests/Tooling/ToolingTest.cpp113
5 files changed, 360 insertions, 1 deletions
diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt
index e64fa1c722..0b3eac95d4 100644
--- a/unittests/CMakeLists.txt
+++ b/unittests/CMakeLists.txt
@@ -65,3 +65,9 @@ add_clang_unittest(Frontend
Frontend/FrontendActionTest.cpp
USED_LIBS gtest gtest_main clangFrontend
)
+
+add_clang_unittest(Tooling
+ Tooling/CompilationDatabaseTest.cpp
+ Tooling/ToolingTest.cpp
+ USED_LIBS gtest gtest_main clangTooling
+ )
diff --git a/unittests/Makefile b/unittests/Makefile
index 66c006232d..05449d8ccf 100644
--- a/unittests/Makefile
+++ b/unittests/Makefile
@@ -14,7 +14,7 @@ ifndef CLANG_LEVEL
IS_UNITTEST_LEVEL := 1
CLANG_LEVEL := ..
-PARALLEL_DIRS = Basic Frontend Lex
+PARALLEL_DIRS = Basic Frontend Lex Tooling
endif # CLANG_LEVEL
diff --git a/unittests/Tooling/CompilationDatabaseTest.cpp b/unittests/Tooling/CompilationDatabaseTest.cpp
new file mode 100644
index 0000000000..9747de2554
--- /dev/null
+++ b/unittests/Tooling/CompilationDatabaseTest.cpp
@@ -0,0 +1,223 @@
+//===- unittest/Tooling/CompilationDatabaseTest.cpp -----------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclGroup.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Tooling/CompilationDatabase.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tooling {
+
+static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName,
+ StringRef JSONDatabase,
+ std::string &ErrorMessage) {
+ llvm::OwningPtr<CompilationDatabase> Database(
+ JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
+ if (!Database)
+ return CompileCommand();
+ std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName);
+ EXPECT_LE(Commands.size(), 1u);
+ if (Commands.empty())
+ return CompileCommand();
+ return Commands[0];
+}
+
+TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) {
+ std::string ErrorMessage;
+ CompileCommand NotFound = findCompileArgsInJsonDatabase(
+ "a-file.cpp", "", ErrorMessage);
+ EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
+ EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
+}
+
+TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) {
+ StringRef Directory("/some/directory");
+ StringRef FileName("/path/to/a-file.cpp");
+ StringRef Command("/path/to/compiler and some arguments");
+ std::string ErrorMessage;
+ CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
+ FileName,
+ ("[{\"directory\":\"" + Directory + "\"," +
+ "\"command\":\"" + Command + "\","
+ "\"file\":\"" + FileName + "\"}]").str(),
+ ErrorMessage);
+ EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
+ ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage;
+ EXPECT_EQ("/path/to/compiler", FoundCommand.CommandLine[0]) << ErrorMessage;
+ EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage;
+ EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage;
+ EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage;
+
+ CompileCommand NotFound = findCompileArgsInJsonDatabase(
+ "a-file.cpp",
+ ("[{\"directory\":\"" + Directory + "\"," +
+ "\"command\":\"" + Command + "\","
+ "\"file\":\"" + FileName + "\"}]").str(),
+ ErrorMessage);
+ EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
+ EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
+}
+
+TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) {
+ StringRef Directory("/some/directory");
+ StringRef FileName("/path/to/a-file.cpp");
+ StringRef Command("\\\"/path to compiler\\\" \\\"and an argument\\\"");
+ std::string ErrorMessage;
+ CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
+ FileName,
+ ("[{\"directory\":\"" + Directory + "\"," +
+ "\"command\":\"" + Command + "\","
+ "\"file\":\"" + FileName + "\"}]").str(),
+ ErrorMessage);
+ ASSERT_EQ(2u, FoundCommand.CommandLine.size());
+ EXPECT_EQ("/path to compiler", FoundCommand.CommandLine[0]) << ErrorMessage;
+ EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage;
+}
+
+TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) {
+ StringRef Directory("/some directory / with spaces");
+ StringRef FileName("/path/to/a-file.cpp");
+ StringRef Command("a command");
+ std::string ErrorMessage;
+ CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
+ FileName,
+ ("[{\"directory\":\"" + Directory + "\"," +
+ "\"command\":\"" + Command + "\","
+ "\"file\":\"" + FileName + "\"}]").str(),
+ ErrorMessage);
+ EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
+}
+
+TEST(findCompileArgsInJsonDatabase, FindsEntry) {
+ StringRef Directory("directory");
+ StringRef FileName("file");
+ StringRef Command("command");
+ std::string JsonDatabase = "[";
+ for (int I = 0; I < 10; ++I) {
+ if (I > 0) JsonDatabase += ",";
+ JsonDatabase +=
+ ("{\"directory\":\"" + Directory + Twine(I) + "\"," +
+ "\"command\":\"" + Command + Twine(I) + "\","
+ "\"file\":\"" + FileName + Twine(I) + "\"}").str();
+ }
+ JsonDatabase += "]";
+ std::string ErrorMessage;
+ CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
+ "file4", JsonDatabase, ErrorMessage);
+ EXPECT_EQ("directory4", FoundCommand.Directory) << ErrorMessage;
+ ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage;
+ EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage;
+}
+
+static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) {
+ std::string JsonDatabase =
+ ("[{\"directory\":\"\", \"file\":\"test\", \"command\": \"" +
+ Command + "\"}]").str();
+ std::string ErrorMessage;
+ CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
+ "test", JsonDatabase, ErrorMessage);
+ EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage;
+ return FoundCommand.CommandLine;
+}
+
+TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) {
+ std::vector<std::string> Result = unescapeJsonCommandLine("");
+ EXPECT_TRUE(Result.empty());
+}
+
+TEST(unescapeJsonCommandLine, SplitsOnSpaces) {
+ std::vector<std::string> Result = unescapeJsonCommandLine("a b c");
+ ASSERT_EQ(3ul, Result.size());
+ EXPECT_EQ("a", Result[0]);
+ EXPECT_EQ("b", Result[1]);
+ EXPECT_EQ("c", Result[2]);
+}
+
+TEST(unescapeJsonCommandLine, MungesMultipleSpaces) {
+ std::vector<std::string> Result = unescapeJsonCommandLine(" a b ");
+ ASSERT_EQ(2ul, Result.size());
+ EXPECT_EQ("a", Result[0]);
+ EXPECT_EQ("b", Result[1]);
+}
+
+TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) {
+ std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\");
+ ASSERT_EQ(1ul, Backslash.size());
+ EXPECT_EQ("a\\", Backslash[0]);
+ std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\"");
+ ASSERT_EQ(1ul, Quote.size());
+ EXPECT_EQ("a\"", Quote[0]);
+}
+
+TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) {
+ std::vector<std::string> Result = unescapeJsonCommandLine("\\\" a b \\\"");
+ ASSERT_EQ(1ul, Result.size());
+ EXPECT_EQ(" a b ", Result[0]);
+}
+
+TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) {
+ std::vector<std::string> Result = unescapeJsonCommandLine(
+ " \\\" a \\\" \\\" b \\\" ");
+ ASSERT_EQ(2ul, Result.size());
+ EXPECT_EQ(" a ", Result[0]);
+ EXPECT_EQ(" b ", Result[1]);
+}
+
+TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) {
+ std::vector<std::string> Result = unescapeJsonCommandLine(
+ "\\\"\\\"\\\"\\\"");
+ ASSERT_EQ(1ul, Result.size());
+ EXPECT_TRUE(Result[0].empty()) << Result[0];
+}
+
+TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) {
+ std::vector<std::string> Result = unescapeJsonCommandLine(
+ "\\\"\\\\\\\"\\\"");
+ ASSERT_EQ(1ul, Result.size());
+ EXPECT_EQ("\"", Result[0]);
+}
+
+TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) {
+ std::vector<std::string> Result = unescapeJsonCommandLine(
+ " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\"");
+ ASSERT_EQ(4ul, Result.size());
+ EXPECT_EQ("\"", Result[0]);
+ EXPECT_EQ("a \" b ", Result[1]);
+ EXPECT_EQ("and\\c", Result[2]);
+ EXPECT_EQ("\"", Result[3]);
+}
+
+TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) {
+ std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine(
+ "\\\"a\\\"\\\"b\\\"");
+ ASSERT_EQ(1ul, QuotedNoSpaces.size());
+ EXPECT_EQ("ab", QuotedNoSpaces[0]);
+
+ std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine(
+ "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");
+ ASSERT_EQ(1ul, MixedNoSpaces.size());
+ EXPECT_EQ("abcdefg", MixedNoSpaces[0]);
+}
+
+TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) {
+ std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc");
+ ASSERT_EQ(1ul, Unclosed.size());
+ EXPECT_EQ("abc", Unclosed[0]);
+
+ std::vector<std::string> Empty = unescapeJsonCommandLine("\\\"");
+ ASSERT_EQ(1ul, Empty.size());
+ EXPECT_EQ("", Empty[0]);
+}
+
+} // end namespace tooling
+} // end namespace clang
diff --git a/unittests/Tooling/Makefile b/unittests/Tooling/Makefile
new file mode 100644
index 0000000000..cc2eeee1d4
--- /dev/null
+++ b/unittests/Tooling/Makefile
@@ -0,0 +1,17 @@
+##===- unittests/Tooling/Makefile --------------------------*- Makefile -*-===##
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+CLANG_LEVEL = ../..
+TESTNAME = Tooling
+LINK_COMPONENTS := support mc
+USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \
+ clangParse.a clangSema.a clangAnalysis.a clangAST.a clangLex.a \
+ clangBasic.a
+
+include $(CLANG_LEVEL)/unittests/Makefile
diff --git a/unittests/Tooling/ToolingTest.cpp b/unittests/Tooling/ToolingTest.cpp
new file mode 100644
index 0000000000..c7b2210a75
--- /dev/null
+++ b/unittests/Tooling/ToolingTest.cpp
@@ -0,0 +1,113 @@
+//===- unittest/Tooling/ToolingTest.cpp - Tooling unit tests --------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclGroup.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Tooling/CompilationDatabase.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tooling {
+
+namespace {
+/// Takes an ast consumer and returns it from CreateASTConsumer. This only
+/// works with single translation unit compilations.
+class TestAction : public clang::ASTFrontendAction {
+ public:
+ /// Takes ownership of TestConsumer.
+ explicit TestAction(clang::ASTConsumer *TestConsumer)
+ : TestConsumer(TestConsumer) {}
+
+ protected:
+ virtual clang::ASTConsumer* CreateASTConsumer(
+ clang::CompilerInstance& compiler, StringRef dummy) {
+ /// TestConsumer will be deleted by the framework calling us.
+ return TestConsumer;
+ }
+
+ private:
+ clang::ASTConsumer * const TestConsumer;
+};
+
+class FindTopLevelDeclConsumer : public clang::ASTConsumer {
+ public:
+ explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl)
+ : FoundTopLevelDecl(FoundTopLevelDecl) {}
+ virtual bool HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) {
+ *FoundTopLevelDecl = true;
+ return true;
+ }
+ private:
+ bool * const FoundTopLevelDecl;
+};
+} // end namespace
+
+TEST(runToolOnCode, FindsTopLevelDeclOnEmptyCode) {
+ bool FoundTopLevelDecl = false;
+ EXPECT_TRUE(runToolOnCode(
+ new TestAction(new FindTopLevelDeclConsumer(&FoundTopLevelDecl)), ""));
+ EXPECT_TRUE(FoundTopLevelDecl);
+}
+
+namespace {
+class FindClassDeclXConsumer : public clang::ASTConsumer {
+ public:
+ FindClassDeclXConsumer(bool *FoundClassDeclX)
+ : FoundClassDeclX(FoundClassDeclX) {}
+ virtual bool HandleTopLevelDecl(clang::DeclGroupRef GroupRef) {
+ if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(
+ *GroupRef.begin())) {
+ if (Record->getName() == "X") {
+ *FoundClassDeclX = true;
+ }
+ }
+ return true;
+ }
+ private:
+ bool *FoundClassDeclX;
+};
+} // end namespace
+
+TEST(runToolOnCode, FindsClassDecl) {
+ bool FoundClassDeclX = false;
+ EXPECT_TRUE(runToolOnCode(new TestAction(
+ new FindClassDeclXConsumer(&FoundClassDeclX)), "class X;"));
+ EXPECT_TRUE(FoundClassDeclX);
+
+ FoundClassDeclX = false;
+ EXPECT_TRUE(runToolOnCode(new TestAction(
+ new FindClassDeclXConsumer(&FoundClassDeclX)), "class Y;"));
+ EXPECT_FALSE(FoundClassDeclX);
+}
+
+TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) {
+ llvm::OwningPtr<FrontendActionFactory> Factory(
+ newFrontendActionFactory<SyntaxOnlyAction>());
+ llvm::OwningPtr<FrontendAction> Action(Factory->create());
+ EXPECT_TRUE(Action.get() != NULL);
+}
+
+struct IndependentFrontendActionCreator {
+ FrontendAction *newFrontendAction() { return new SyntaxOnlyAction; }
+};
+
+TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) {
+ IndependentFrontendActionCreator Creator;
+ llvm::OwningPtr<FrontendActionFactory> Factory(
+ newFrontendActionFactory(&Creator));
+ llvm::OwningPtr<FrontendAction> Action(Factory->create());
+ EXPECT_TRUE(Action.get() != NULL);
+}
+
+} // end namespace tooling
+} // end namespace clang