llvm-project/clang-tools-extra/unittests/clang-modernize/TransformTest.cpp

300 lines
9.5 KiB
C++

//===- clang-modernize/TransformTest.cpp - Transform unit tests -----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "gtest/gtest.h"
#include "Core/Transform.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclGroup.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
using namespace clang;
using namespace ast_matchers;
class DummyTransform : public Transform {
public:
DummyTransform(llvm::StringRef Name, const TransformOptions &Options)
: Transform(Name, Options) {}
int apply(const tooling::CompilationDatabase &,
const std::vector<std::string> &) override {
return 0;
}
void setAcceptedChanges(unsigned Changes) {
Transform::setAcceptedChanges(Changes);
}
void setRejectedChanges(unsigned Changes) {
Transform::setRejectedChanges(Changes);
}
void setDeferredChanges(unsigned Changes) {
Transform::setDeferredChanges(Changes);
}
};
TEST(Transform, Interface) {
TransformOptions Options;
DummyTransform T("my_transform", Options);
ASSERT_EQ("my_transform", T.getName());
ASSERT_EQ(0u, T.getAcceptedChanges());
ASSERT_EQ(0u, T.getRejectedChanges());
ASSERT_EQ(0u, T.getDeferredChanges());
ASSERT_FALSE(T.getChangesMade());
ASSERT_FALSE(T.getChangesNotMade());
T.setAcceptedChanges(1);
ASSERT_TRUE(T.getChangesMade());
T.setDeferredChanges(1);
ASSERT_TRUE(T.getChangesNotMade());
T.setRejectedChanges(1);
ASSERT_TRUE(T.getChangesNotMade());
T.Reset();
ASSERT_EQ(0u, T.getAcceptedChanges());
ASSERT_EQ(0u, T.getRejectedChanges());
ASSERT_EQ(0u, T.getDeferredChanges());
T.setRejectedChanges(1);
ASSERT_TRUE(T.getChangesNotMade());
}
class TimePassingASTConsumer : public ASTConsumer {
public:
TimePassingASTConsumer(bool *Called) : Called(Called) {}
bool HandleTopLevelDecl(DeclGroupRef DeclGroup) override {
llvm::sys::TimeValue UserStart;
llvm::sys::TimeValue SystemStart;
llvm::sys::TimeValue UserNow;
llvm::sys::TimeValue SystemNow;
llvm::sys::TimeValue Wall;
// Busy-wait until the user/system time combined is more than 1ms
llvm::sys::TimeValue OneMS(0, 1000000);
llvm::sys::Process::GetTimeUsage(Wall, UserStart, SystemStart);
do {
llvm::sys::Process::GetTimeUsage(Wall, UserNow, SystemNow);
} while (UserNow - UserStart + SystemNow - SystemStart < OneMS);
*Called = true;
return true;
}
bool *Called;
};
struct ConsumerFactory {
std::unique_ptr<ASTConsumer> newASTConsumer() {
return llvm::make_unique<TimePassingASTConsumer>(&Called);
}
bool Called;
};
struct CallbackForwarder : public clang::tooling::SourceFileCallbacks {
CallbackForwarder(Transform &Callee) : Callee(Callee) {}
bool handleBeginSource(CompilerInstance &CI, StringRef Filename) override {
return Callee.handleBeginSource(CI, Filename);
}
void handleEndSource() override { Callee.handleEndSource(); }
Transform &Callee;
};
TEST(Transform, Timings) {
TransformOptions Options;
Options.EnableTiming = true;
DummyTransform T("timing_transform", Options);
// All the path stuff is to make the test work independently of OS.
// The directory used is not important since the path gets mapped to a virtual
// file anyway. What is important is that we have an absolute path with which
// to use with mapVirtualFile().
SmallString<128> CurrentDir;
std::error_code EC = llvm::sys::fs::current_path(CurrentDir);
assert(!EC);
(void)EC;
SmallString<128> FileA = CurrentDir;
llvm::sys::path::append(FileA, "a.cc");
SmallString<128> FileB = CurrentDir;
llvm::sys::path::append(FileB, "b.cc");
tooling::FixedCompilationDatabase Compilations(CurrentDir.str(),
std::vector<std::string>());
std::vector<std::string> Sources;
Sources.push_back(FileA.str());
Sources.push_back(FileB.str());
tooling::ClangTool Tool(Compilations, Sources);
Tool.mapVirtualFile(FileA, "void a() {}");
Tool.mapVirtualFile(FileB, "void b() {}");
// Factory to create TimePassingASTConsumer for each source file the tool
// runs on.
ConsumerFactory Factory;
// We don't care about any of Transform's functionality except to get it to
// record timings. For that, we need to forward handleBeginSource() and
// handleEndSource() calls to it.
CallbackForwarder Callbacks(T);
Tool.run(
clang::tooling::newFrontendActionFactory(&Factory, &Callbacks).get());
EXPECT_TRUE(Factory.Called);
Transform::TimingVec::const_iterator I = T.timing_begin();
EXPECT_GT(I->second.getProcessTime(), 0.0);
// The success of the test shouldn't depend on the order of iteration through
// timers.
StringRef FirstFile = I->first;
if (FileA == FirstFile) {
++I;
EXPECT_EQ(FileB, I->first);
EXPECT_GT(I->second.getProcessTime(), 0.0);
} else if (FileB == FirstFile) {
++I;
EXPECT_EQ(FileA, I->first);
EXPECT_GT(I->second.getProcessTime(), 0.0);
} else {
FAIL() << "Unexpected file name " << I->first << " in timing data.";
}
++I;
EXPECT_EQ(T.timing_end(), I);
}
class ModifiableCallback
: public clang::ast_matchers::MatchFinder::MatchCallback {
public:
ModifiableCallback(const Transform &Owner)
: Owner(Owner) {}
void
run(const clang::ast_matchers::MatchFinder::MatchResult &Result) override {
const VarDecl *Decl = Result.Nodes.getNodeAs<VarDecl>("decl");
ASSERT_TRUE(Decl != nullptr);
const SourceManager &SM = *Result.SourceManager;
// Decl 'a' comes from the main source file. This test should always pass.
if (Decl->getName().equals("a"))
EXPECT_TRUE(Owner.isFileModifiable(SM, Decl->getLocStart()));
// Decl 'c' comes from an excluded header. This test should never pass.
else if (Decl->getName().equals("c"))
EXPECT_FALSE(Owner.isFileModifiable(SM, Decl->getLocStart()));
// Decl 'b' comes from an included header.
else if (Decl->getName().equals("b"))
EXPECT_TRUE(Owner.isFileModifiable(SM, Decl->getLocStart()));
// Make sure edge cases are handled gracefully (they should never be
// allowed).
SourceLocation DummyLoc;
EXPECT_FALSE(Owner.isFileModifiable(SM, DummyLoc));
}
private:
const Transform &Owner;
};
TEST(Transform, isFileModifiable) {
TransformOptions Options;
///
/// SETUP
///
/// To test Transform::isFileModifiable() we need a SourceManager primed with
/// actual files and SourceLocations to test. Easiest way to accomplish this
/// is to use Tooling classes.
///
/// 1) Simulate a source file that includes two headers, one that is allowed
/// to be modified and the other that is not allowed. Each of the three
/// files involved will declare a single variable with a different name.
/// 2) A matcher is created to find VarDecls.
/// 3) A MatchFinder callback calls Transform::isFileModifiable() with the
/// SourceLocations of found VarDecls and thus tests the function.
///
// All the path stuff is to make the test work independently of OS.
// The directory used is not important since the path gets mapped to a virtual
// file anyway. What is important is that we have an absolute path with which
// to use with mapVirtualFile().
SmallString<128> CurrentDir;
std::error_code EC = llvm::sys::fs::current_path(CurrentDir);
assert(!EC);
(void)EC;
SmallString<128> SourceFile = CurrentDir;
llvm::sys::path::append(SourceFile, "a.cc");
SmallString<128> HeaderFile = CurrentDir;
llvm::sys::path::append(HeaderFile, "a.h");
SmallString<128> HeaderBFile = CurrentDir;
llvm::sys::path::append(HeaderBFile, "temp");
llvm::sys::path::append(HeaderBFile, "b.h");
StringRef ExcludeDir = llvm::sys::path::parent_path(HeaderBFile);
IncludeExcludeInfo IncInfo;
Options.ModifiableFiles.readListFromString(CurrentDir, ExcludeDir);
tooling::FixedCompilationDatabase Compilations(CurrentDir.str(),
std::vector<std::string>());
std::vector<std::string> Sources;
Sources.push_back(SourceFile.str());
tooling::ClangTool Tool(Compilations, Sources);
Tool.mapVirtualFile(SourceFile,
"#include \"a.h\"\n"
"#include \"temp/b.h\"\n"
"int a;");
Tool.mapVirtualFile(HeaderFile, "int b;");
Tool.mapVirtualFile(HeaderBFile, "int c;");
DummyTransform T("dummy", Options);
MatchFinder Finder;
ModifiableCallback Callback(T);
Finder.addMatcher(varDecl().bind("decl"), &Callback);
Tool.run(tooling::newFrontendActionFactory(&Finder).get());
}
TEST(VersionTest, Interface) {
Version V;
ASSERT_TRUE(V.isNull());
ASSERT_TRUE(Version(1) < Version(1, 1));
ASSERT_TRUE(Version(1) < Version(2));
ASSERT_TRUE(Version(1, 1) < Version(2));
ASSERT_TRUE(Version(1, 1) == Version(1, 1));
ASSERT_EQ(Version(1).getMajor(), unsigned(1));
ASSERT_EQ(Version(1).getMinor(), unsigned(0));
ASSERT_EQ(Version(1, 2).getMinor(), unsigned(2));
}
TEST(VersionTest, getFromString) {
ASSERT_EQ(Version(1), Version::getFromString("1"));
ASSERT_EQ(Version(1, 2), Version::getFromString("1.2"));
ASSERT_TRUE(Version::getFromString("foo").isNull());
ASSERT_TRUE(Version::getFromString("1bar").isNull());
// elements after major.minor are ignored
ASSERT_EQ(Version(1, 2), Version::getFromString("1.2.3"));
}