//===- cpp11-migrate/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/FileOverrides.h" #include "Core/Transform.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/DeclGroup.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Process.h" #include "llvm/Support/Path.h" using namespace clang; using namespace ast_matchers; class DummyTransform : public Transform { public: DummyTransform(llvm::StringRef Name, const TransformOptions &Options) : Transform(Name, Options) {} virtual int apply(const FileOverrides &, const tooling::CompilationDatabase &, const std::vector &) { return 0; } void setAcceptedChanges(unsigned Changes) { Transform::setAcceptedChanges(Changes); } void setRejectedChanges(unsigned Changes) { Transform::setRejectedChanges(Changes); } void setDeferredChanges(unsigned Changes) { Transform::setDeferredChanges(Changes); } void setOverrides(FileOverrides &Overrides) { Transform::setOverrides(Overrides); } }; 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) {} virtual bool HandleTopLevelDecl(DeclGroupRef DeclGroup) { 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 { ASTConsumer *newASTConsumer() { return new TimePassingASTConsumer(&Called); } bool Called; }; struct CallbackForwarder : public clang::tooling::SourceFileCallbacks { CallbackForwarder(Transform &Callee) : Callee(Callee) {} virtual bool handleBeginSource(CompilerInstance &CI, StringRef Filename) { return Callee.handleBeginSource(CI, Filename); } virtual void handleEndSource() { 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; llvm::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::vector 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); // Transform's handle* functions require FileOverrides to be set, even if // there aren't any. FileOverrides Overrides; T.setOverrides(Overrides); Tool.run(clang::tooling::newFrontendActionFactory(&Factory, &Callbacks)); 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, bool HeadersModifiable) : Owner(Owner), HeadersModifiable(HeadersModifiable) {} virtual void run(const clang::ast_matchers::MatchFinder::MatchResult &Result) { const VarDecl *Decl = Result.Nodes.getNodeAs("decl"); ASSERT_TRUE(Decl != 0); 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. It should be modifiable only if // header modifications are allowed. else if (Decl->getName().equals("b")) EXPECT_EQ(HeadersModifiable, 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; bool HeadersModifiable; }; 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; llvm::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.ModifiableHeaders.readListFromString(CurrentDir, ExcludeDir); tooling::FixedCompilationDatabase Compilations(CurrentDir.str(), std::vector()); std::vector 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;"); // Run tests with header modifications turned off. { SCOPED_TRACE("Header Modifications are OFF"); Options.EnableHeaderModifications = false; DummyTransform T("dummy", Options); MatchFinder Finder; Finder.addMatcher(varDecl().bind("decl"), new ModifiableCallback(T, false)); Tool.run(tooling::newFrontendActionFactory(&Finder)); } // Run again with header modifications turned on. { SCOPED_TRACE("Header Modifications are ON"); Options.EnableHeaderModifications = true; DummyTransform T("dummy", Options); MatchFinder Finder; Finder.addMatcher(varDecl().bind("decl"), new ModifiableCallback(T, true)); Tool.run(tooling::newFrontendActionFactory(&Finder)); } } 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")); }