298 lines
10 KiB
C++
298 lines
10 KiB
C++
|
//===- unittest/Tooling/ExecutionTest.cpp - Tool execution tests. --------===//
|
||
|
//
|
||
|
// 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/Execution.h"
|
||
|
#include "clang/AST/ASTConsumer.h"
|
||
|
#include "clang/AST/DeclCXX.h"
|
||
|
#include "clang/AST/RecursiveASTVisitor.h"
|
||
|
#include "clang/Frontend/ASTUnit.h"
|
||
|
#include "clang/Frontend/FrontendAction.h"
|
||
|
#include "clang/Frontend/FrontendActions.h"
|
||
|
#include "clang/Tooling/AllTUsExecution.h"
|
||
|
#include "clang/Tooling/CompilationDatabase.h"
|
||
|
#include "clang/Tooling/StandaloneExecution.h"
|
||
|
#include "clang/Tooling/ToolExecutorPluginRegistry.h"
|
||
|
#include "clang/Tooling/Tooling.h"
|
||
|
#include "gmock/gmock.h"
|
||
|
#include "gtest/gtest.h"
|
||
|
#include <algorithm>
|
||
|
#include <string>
|
||
|
|
||
|
namespace clang {
|
||
|
namespace tooling {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// This traverses the AST and outputs function name as key and "1" as value for
|
||
|
// each function declaration.
|
||
|
class ASTConsumerWithResult
|
||
|
: public ASTConsumer,
|
||
|
public RecursiveASTVisitor<ASTConsumerWithResult> {
|
||
|
public:
|
||
|
using ASTVisitor = RecursiveASTVisitor<ASTConsumerWithResult>;
|
||
|
|
||
|
explicit ASTConsumerWithResult(ExecutionContext *Context) : Context(Context) {
|
||
|
assert(Context != nullptr);
|
||
|
}
|
||
|
|
||
|
void HandleTranslationUnit(clang::ASTContext &Context) override {
|
||
|
TraverseDecl(Context.getTranslationUnitDecl());
|
||
|
}
|
||
|
|
||
|
bool TraverseFunctionDecl(clang::FunctionDecl *Decl) {
|
||
|
Context->reportResult(Decl->getNameAsString(),
|
||
|
Context->getRevision() + ":" + Context->getCorpus() +
|
||
|
":" + Context->getCurrentCompilationUnit() +
|
||
|
"/1");
|
||
|
return ASTVisitor::TraverseFunctionDecl(Decl);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
ExecutionContext *const Context;
|
||
|
};
|
||
|
|
||
|
class ReportResultAction : public ASTFrontendAction {
|
||
|
public:
|
||
|
explicit ReportResultAction(ExecutionContext *Context) : Context(Context) {
|
||
|
assert(Context != nullptr);
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
std::unique_ptr<clang::ASTConsumer>
|
||
|
CreateASTConsumer(clang::CompilerInstance &compiler,
|
||
|
StringRef /* dummy */) override {
|
||
|
std::unique_ptr<clang::ASTConsumer> ast_consumer{
|
||
|
new ASTConsumerWithResult(Context)};
|
||
|
return ast_consumer;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
ExecutionContext *const Context;
|
||
|
};
|
||
|
|
||
|
class ReportResultActionFactory : public FrontendActionFactory {
|
||
|
public:
|
||
|
ReportResultActionFactory(ExecutionContext *Context) : Context(Context) {}
|
||
|
std::unique_ptr<FrontendAction> create() override {
|
||
|
return std::make_unique<ReportResultAction>(Context);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
ExecutionContext *const Context;
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
class TestToolExecutor : public ToolExecutor {
|
||
|
public:
|
||
|
static const char *ExecutorName;
|
||
|
|
||
|
TestToolExecutor(CommonOptionsParser Options)
|
||
|
: OptionsParser(std::move(Options)) {}
|
||
|
|
||
|
StringRef getExecutorName() const override { return ExecutorName; }
|
||
|
|
||
|
llvm::Error
|
||
|
execute(llvm::ArrayRef<std::pair<std::unique_ptr<FrontendActionFactory>,
|
||
|
ArgumentsAdjuster>>) override {
|
||
|
return llvm::Error::success();
|
||
|
}
|
||
|
|
||
|
ExecutionContext *getExecutionContext() override { return nullptr; };
|
||
|
|
||
|
ToolResults *getToolResults() override { return nullptr; }
|
||
|
|
||
|
llvm::ArrayRef<std::string> getSourcePaths() const {
|
||
|
return OptionsParser.getSourcePathList();
|
||
|
}
|
||
|
|
||
|
void mapVirtualFile(StringRef FilePath, StringRef Content) override {
|
||
|
VFS[std::string(FilePath)] = std::string(Content);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
CommonOptionsParser OptionsParser;
|
||
|
std::string SourcePaths;
|
||
|
std::map<std::string, std::string> VFS;
|
||
|
};
|
||
|
|
||
|
const char *TestToolExecutor::ExecutorName = "test-executor";
|
||
|
|
||
|
class TestToolExecutorPlugin : public ToolExecutorPlugin {
|
||
|
public:
|
||
|
llvm::Expected<std::unique_ptr<ToolExecutor>>
|
||
|
create(CommonOptionsParser &OptionsParser) override {
|
||
|
return std::make_unique<TestToolExecutor>(std::move(OptionsParser));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static ToolExecutorPluginRegistry::Add<TestToolExecutorPlugin>
|
||
|
X("test-executor", "Plugin for TestToolExecutor.");
|
||
|
|
||
|
llvm::cl::OptionCategory TestCategory("execution-test options");
|
||
|
|
||
|
TEST(CreateToolExecutorTest, FailedCreateExecutorUndefinedFlag) {
|
||
|
std::vector<const char *> argv = {"prog", "--fake_flag_no_no_no", "f"};
|
||
|
int argc = argv.size();
|
||
|
auto Executor = internal::createExecutorFromCommandLineArgsImpl(
|
||
|
argc, &argv[0], TestCategory);
|
||
|
ASSERT_FALSE((bool)Executor);
|
||
|
llvm::consumeError(Executor.takeError());
|
||
|
}
|
||
|
|
||
|
TEST(CreateToolExecutorTest, RegisterFlagsBeforeReset) {
|
||
|
llvm::cl::opt<std::string> BeforeReset(
|
||
|
"before_reset", llvm::cl::desc("Defined before reset."),
|
||
|
llvm::cl::init(""));
|
||
|
|
||
|
llvm::cl::ResetAllOptionOccurrences();
|
||
|
|
||
|
std::vector<const char *> argv = {"prog", "--before_reset=set", "f"};
|
||
|
int argc = argv.size();
|
||
|
auto Executor = internal::createExecutorFromCommandLineArgsImpl(
|
||
|
argc, &argv[0], TestCategory);
|
||
|
ASSERT_TRUE((bool)Executor);
|
||
|
EXPECT_EQ(BeforeReset, "set");
|
||
|
BeforeReset.removeArgument();
|
||
|
}
|
||
|
|
||
|
TEST(CreateToolExecutorTest, CreateStandaloneToolExecutor) {
|
||
|
std::vector<const char *> argv = {"prog", "standalone.cpp"};
|
||
|
int argc = argv.size();
|
||
|
auto Executor = internal::createExecutorFromCommandLineArgsImpl(
|
||
|
argc, &argv[0], TestCategory);
|
||
|
ASSERT_TRUE((bool)Executor);
|
||
|
EXPECT_EQ(Executor->get()->getExecutorName(),
|
||
|
StandaloneToolExecutor::ExecutorName);
|
||
|
}
|
||
|
|
||
|
TEST(CreateToolExecutorTest, CreateTestToolExecutor) {
|
||
|
std::vector<const char *> argv = {"prog", "test.cpp",
|
||
|
"--executor=test-executor"};
|
||
|
int argc = argv.size();
|
||
|
auto Executor = internal::createExecutorFromCommandLineArgsImpl(
|
||
|
argc, &argv[0], TestCategory);
|
||
|
ASSERT_TRUE((bool)Executor);
|
||
|
EXPECT_EQ(Executor->get()->getExecutorName(), TestToolExecutor::ExecutorName);
|
||
|
}
|
||
|
|
||
|
TEST(StandaloneToolTest, SynctaxOnlyActionOnSimpleCode) {
|
||
|
FixedCompilationDatabase Compilations(".", std::vector<std::string>());
|
||
|
StandaloneToolExecutor Executor(Compilations,
|
||
|
std::vector<std::string>(1, "a.cc"));
|
||
|
Executor.mapVirtualFile("a.cc", "int x = 0;");
|
||
|
|
||
|
auto Err = Executor.execute(newFrontendActionFactory<SyntaxOnlyAction>(),
|
||
|
getClangSyntaxOnlyAdjuster());
|
||
|
ASSERT_TRUE(!Err);
|
||
|
}
|
||
|
|
||
|
TEST(StandaloneToolTest, SimpleAction) {
|
||
|
FixedCompilationDatabase Compilations(".", std::vector<std::string>());
|
||
|
StandaloneToolExecutor Executor(Compilations,
|
||
|
std::vector<std::string>(1, "a.cc"));
|
||
|
Executor.mapVirtualFile("a.cc", "int x = 0;");
|
||
|
|
||
|
auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
|
||
|
new ReportResultActionFactory(Executor.getExecutionContext())));
|
||
|
ASSERT_TRUE(!Err);
|
||
|
auto KVs = Executor.getToolResults()->AllKVResults();
|
||
|
ASSERT_EQ(KVs.size(), 0u);
|
||
|
}
|
||
|
|
||
|
TEST(StandaloneToolTest, SimpleActionWithResult) {
|
||
|
FixedCompilationDatabase Compilations(".", std::vector<std::string>());
|
||
|
StandaloneToolExecutor Executor(Compilations,
|
||
|
std::vector<std::string>(1, "a.cc"));
|
||
|
Executor.mapVirtualFile("a.cc", "int x = 0; void f() {}");
|
||
|
|
||
|
auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
|
||
|
new ReportResultActionFactory(Executor.getExecutionContext())));
|
||
|
ASSERT_TRUE(!Err);
|
||
|
auto KVs = Executor.getToolResults()->AllKVResults();
|
||
|
ASSERT_EQ(KVs.size(), 1u);
|
||
|
EXPECT_EQ("f", KVs[0].first);
|
||
|
// Currently the standlone executor returns empty corpus, revision, and
|
||
|
// compilation unit.
|
||
|
EXPECT_EQ("::/1", KVs[0].second);
|
||
|
|
||
|
Executor.getToolResults()->forEachResult(
|
||
|
[](StringRef, StringRef Value) { EXPECT_EQ("::/1", Value); });
|
||
|
}
|
||
|
|
||
|
class FixedCompilationDatabaseWithFiles : public CompilationDatabase {
|
||
|
public:
|
||
|
FixedCompilationDatabaseWithFiles(Twine Directory,
|
||
|
ArrayRef<std::string> Files,
|
||
|
ArrayRef<std::string> CommandLine)
|
||
|
: FixedCompilations(Directory, CommandLine), Files(Files) {}
|
||
|
|
||
|
std::vector<CompileCommand>
|
||
|
getCompileCommands(StringRef FilePath) const override {
|
||
|
return FixedCompilations.getCompileCommands(FilePath);
|
||
|
}
|
||
|
|
||
|
std::vector<std::string> getAllFiles() const override { return Files; }
|
||
|
|
||
|
private:
|
||
|
FixedCompilationDatabase FixedCompilations;
|
||
|
std::vector<std::string> Files;
|
||
|
};
|
||
|
|
||
|
MATCHER_P(Named, Name, "") { return arg.first == Name; }
|
||
|
|
||
|
TEST(AllTUsToolTest, AFewFiles) {
|
||
|
FixedCompilationDatabaseWithFiles Compilations(
|
||
|
".", {"a.cc", "b.cc", "c.cc", "ignore.cc"}, std::vector<std::string>());
|
||
|
AllTUsToolExecutor Executor(Compilations, /*ThreadCount=*/0);
|
||
|
Filter.setValue("[a-c].cc");
|
||
|
Executor.mapVirtualFile("a.cc", "void x() {}");
|
||
|
Executor.mapVirtualFile("b.cc", "void y() {}");
|
||
|
Executor.mapVirtualFile("c.cc", "void z() {}");
|
||
|
Executor.mapVirtualFile("ignore.cc", "void d() {}");
|
||
|
|
||
|
auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
|
||
|
new ReportResultActionFactory(Executor.getExecutionContext())));
|
||
|
ASSERT_TRUE(!Err);
|
||
|
EXPECT_THAT(
|
||
|
Executor.getToolResults()->AllKVResults(),
|
||
|
::testing::UnorderedElementsAre(Named("x"), Named("y"), Named("z")));
|
||
|
Filter.setValue(".*"); // reset to default value.
|
||
|
}
|
||
|
|
||
|
TEST(AllTUsToolTest, ManyFiles) {
|
||
|
unsigned NumFiles = 100;
|
||
|
std::vector<std::string> Files;
|
||
|
std::map<std::string, std::string> FileToContent;
|
||
|
std::vector<std::string> ExpectedSymbols;
|
||
|
for (unsigned i = 1; i <= NumFiles; ++i) {
|
||
|
std::string File = "f" + std::to_string(i) + ".cc";
|
||
|
std::string Symbol = "looong_function_name_" + std::to_string(i);
|
||
|
Files.push_back(File);
|
||
|
FileToContent[File] = "void " + Symbol + "() {}";
|
||
|
ExpectedSymbols.push_back(Symbol);
|
||
|
}
|
||
|
FixedCompilationDatabaseWithFiles Compilations(".", Files,
|
||
|
std::vector<std::string>());
|
||
|
AllTUsToolExecutor Executor(Compilations, /*ThreadCount=*/0);
|
||
|
for (const auto &FileAndContent : FileToContent) {
|
||
|
Executor.mapVirtualFile(FileAndContent.first, FileAndContent.second);
|
||
|
}
|
||
|
|
||
|
auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
|
||
|
new ReportResultActionFactory(Executor.getExecutionContext())));
|
||
|
ASSERT_TRUE(!Err);
|
||
|
std::vector<std::string> Results;
|
||
|
Executor.getToolResults()->forEachResult(
|
||
|
[&](StringRef Name, StringRef) { Results.push_back(std::string(Name)); });
|
||
|
EXPECT_THAT(ExpectedSymbols, ::testing::UnorderedElementsAreArray(Results));
|
||
|
}
|
||
|
|
||
|
} // end namespace tooling
|
||
|
} // end namespace clang
|