blob: 931c761178047355b5a31d0d6ce39385ed512dbe [file] [log] [blame]
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +00001//===- unittest/Tooling/RefactoringTest.cpp - Refactoring unit tests ------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "RewriterTestContext.h"
11#include "clang/AST/ASTConsumer.h"
12#include "clang/AST/DeclCXX.h"
13#include "clang/AST/DeclGroup.h"
14#include "clang/AST/RecursiveASTVisitor.h"
15#include "clang/Tooling/Refactoring.h"
16#include "clang/Basic/Diagnostic.h"
17#include "clang/Basic/FileManager.h"
18#include "clang/Basic/LangOptions.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Frontend/CompilerInstance.h"
21#include "clang/Frontend/DiagnosticOptions.h"
22#include "clang/Frontend/FrontendAction.h"
23#include "clang/Frontend/TextDiagnosticPrinter.h"
24#include "clang/Rewrite/Rewriter.h"
25#include "clang/Tooling/Tooling.h"
26#include "llvm/ADT/SmallString.h"
27#include "llvm/Support/Path.h"
28#include "gtest/gtest.h"
29
30namespace clang {
31namespace tooling {
32
33class ReplacementTest : public ::testing::Test {
34 protected:
35 Replacement createReplacement(SourceLocation Start, unsigned Length,
36 llvm::StringRef ReplacementText) {
37 return Replacement(Context.Sources, Start, Length, ReplacementText);
38 }
39
40 RewriterTestContext Context;
41};
42
43TEST_F(ReplacementTest, CanDeleteAllText) {
44 FileID ID = Context.createInMemoryFile("input.cpp", "text");
45 SourceLocation Location = Context.getLocation(ID, 1, 1);
46 Replacement Replace(createReplacement(Location, 4, ""));
47 EXPECT_TRUE(Replace.apply(Context.Rewrite));
48 EXPECT_EQ("", Context.getRewrittenText(ID));
49}
50
51TEST_F(ReplacementTest, CanDeleteAllTextInTextWithNewlines) {
52 FileID ID = Context.createInMemoryFile("input.cpp", "line1\nline2\nline3");
53 SourceLocation Location = Context.getLocation(ID, 1, 1);
54 Replacement Replace(createReplacement(Location, 17, ""));
55 EXPECT_TRUE(Replace.apply(Context.Rewrite));
56 EXPECT_EQ("", Context.getRewrittenText(ID));
57}
58
59TEST_F(ReplacementTest, CanAddText) {
60 FileID ID = Context.createInMemoryFile("input.cpp", "");
61 SourceLocation Location = Context.getLocation(ID, 1, 1);
62 Replacement Replace(createReplacement(Location, 0, "result"));
63 EXPECT_TRUE(Replace.apply(Context.Rewrite));
64 EXPECT_EQ("result", Context.getRewrittenText(ID));
65}
66
67TEST_F(ReplacementTest, CanReplaceTextAtPosition) {
68 FileID ID = Context.createInMemoryFile("input.cpp",
69 "line1\nline2\nline3\nline4");
70 SourceLocation Location = Context.getLocation(ID, 2, 3);
71 Replacement Replace(createReplacement(Location, 12, "x"));
72 EXPECT_TRUE(Replace.apply(Context.Rewrite));
73 EXPECT_EQ("line1\nlixne4", Context.getRewrittenText(ID));
74}
75
76TEST_F(ReplacementTest, CanReplaceTextAtPositionMultipleTimes) {
77 FileID ID = Context.createInMemoryFile("input.cpp",
78 "line1\nline2\nline3\nline4");
79 SourceLocation Location1 = Context.getLocation(ID, 2, 3);
80 Replacement Replace1(createReplacement(Location1, 12, "x\ny\n"));
81 EXPECT_TRUE(Replace1.apply(Context.Rewrite));
82 EXPECT_EQ("line1\nlix\ny\nne4", Context.getRewrittenText(ID));
83
84 // Since the original source has not been modified, the (4, 4) points to the
85 // 'e' in the original content.
86 SourceLocation Location2 = Context.getLocation(ID, 4, 4);
87 Replacement Replace2(createReplacement(Location2, 1, "f"));
88 EXPECT_TRUE(Replace2.apply(Context.Rewrite));
89 EXPECT_EQ("line1\nlix\ny\nnf4", Context.getRewrittenText(ID));
90}
91
92TEST_F(ReplacementTest, ApplyFailsForNonExistentLocation) {
93 Replacement Replace("nonexistent-file.cpp", 0, 1, "");
94 EXPECT_FALSE(Replace.apply(Context.Rewrite));
95}
96
97TEST_F(ReplacementTest, CanRetrivePath) {
98 Replacement Replace("/path/to/file.cpp", 0, 1, "");
99 EXPECT_EQ("/path/to/file.cpp", Replace.getFilePath());
100}
101
102TEST_F(ReplacementTest, ReturnsInvalidPath) {
103 Replacement Replace1(Context.Sources, SourceLocation(), 0, "");
104 EXPECT_TRUE(Replace1.getFilePath().empty());
105
106 Replacement Replace2;
107 EXPECT_TRUE(Replace2.getFilePath().empty());
108}
109
110TEST_F(ReplacementTest, CanApplyReplacements) {
111 FileID ID = Context.createInMemoryFile("input.cpp",
112 "line1\nline2\nline3\nline4");
113 Replacements Replaces;
114 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
115 5, "replaced"));
116 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 3, 1),
117 5, "other"));
118 EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
119 EXPECT_EQ("line1\nreplaced\nother\nline4", Context.getRewrittenText(ID));
120}
121
122TEST_F(ReplacementTest, SkipsDuplicateReplacements) {
123 FileID ID = Context.createInMemoryFile("input.cpp",
124 "line1\nline2\nline3\nline4");
125 Replacements Replaces;
126 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
127 5, "replaced"));
128 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
129 5, "replaced"));
130 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
131 5, "replaced"));
132 EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
133 EXPECT_EQ("line1\nreplaced\nline3\nline4", Context.getRewrittenText(ID));
134}
135
136TEST_F(ReplacementTest, ApplyAllFailsIfOneApplyFails) {
137 // This test depends on the value of the file name of an invalid source
138 // location being in the range ]a, z[.
139 FileID IDa = Context.createInMemoryFile("a.cpp", "text");
140 FileID IDz = Context.createInMemoryFile("z.cpp", "text");
141 Replacements Replaces;
142 Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDa, 1, 1),
143 4, "a"));
144 Replaces.insert(Replacement(Context.Sources, SourceLocation(),
145 5, "2"));
146 Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDz, 1, 1),
147 4, "z"));
148 EXPECT_FALSE(applyAllReplacements(Replaces, Context.Rewrite));
149 EXPECT_EQ("a", Context.getRewrittenText(IDa));
150 EXPECT_EQ("z", Context.getRewrittenText(IDz));
151}
152
153class FlushRewrittenFilesTest : public ::testing::Test {
154 public:
155 FlushRewrittenFilesTest() {
156 std::string ErrorInfo;
157 TemporaryDirectory = llvm::sys::Path::GetTemporaryDirectory(&ErrorInfo);
158 assert(ErrorInfo.empty());
159 }
160
161 ~FlushRewrittenFilesTest() {
162 std::string ErrorInfo;
163 TemporaryDirectory.eraseFromDisk(true, &ErrorInfo);
164 assert(ErrorInfo.empty());
165 }
166
167 FileID createFile(llvm::StringRef Name, llvm::StringRef Content) {
168 llvm::SmallString<1024> Path(TemporaryDirectory.str());
169 llvm::sys::path::append(Path, Name);
170 std::string ErrorInfo;
171 llvm::raw_fd_ostream OutStream(Path.c_str(),
172 ErrorInfo, llvm::raw_fd_ostream::F_Binary);
173 assert(ErrorInfo.empty());
174 OutStream << Content;
175 OutStream.close();
176 const FileEntry *File = Context.Files.getFile(Path);
177 assert(File != NULL);
178 return Context.Sources.createFileID(File, SourceLocation(), SrcMgr::C_User);
179 }
180
181 std::string getFileContentFromDisk(llvm::StringRef Name) {
182 llvm::SmallString<1024> Path(TemporaryDirectory.str());
183 llvm::sys::path::append(Path, Name);
184 // We need to read directly from the FileManager without relaying through
185 // a FileEntry, as otherwise we'd read through an already opened file
186 // descriptor, which might not see the changes made.
187 // FIXME: Figure out whether there is a way to get the SourceManger to
188 // reopen the file.
189 return Context.Files.getBufferForFile(Path, NULL)->getBuffer();
190 }
191
192 llvm::sys::Path TemporaryDirectory;
193 RewriterTestContext Context;
194};
195
196TEST_F(FlushRewrittenFilesTest, StoresChangesOnDisk) {
197 FileID ID = createFile("input.cpp", "line1\nline2\nline3\nline4");
198 Replacements Replaces;
199 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
200 5, "replaced"));
201 EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
202 EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles());
203 EXPECT_EQ("line1\nreplaced\nline3\nline4",
204 getFileContentFromDisk("input.cpp"));
205}
206
207namespace {
208template <typename T>
209class TestVisitor : public clang::RecursiveASTVisitor<T> {
210public:
211 bool runOver(StringRef Code) {
212 return runToolOnCode(new TestAction(this), Code);
213 }
214
215protected:
216 clang::SourceManager *SM;
217
218private:
219 class FindConsumer : public clang::ASTConsumer {
220 public:
221 FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {}
222
223 virtual void HandleTranslationUnit(clang::ASTContext &Context) {
224 Visitor->TraverseDecl(Context.getTranslationUnitDecl());
225 }
226
227 private:
228 TestVisitor *Visitor;
229 };
230
231 class TestAction : public clang::ASTFrontendAction {
232 public:
233 TestAction(TestVisitor *Visitor) : Visitor(Visitor) {}
234
235 virtual clang::ASTConsumer* CreateASTConsumer(
236 clang::CompilerInstance& compiler, llvm::StringRef dummy) {
237 Visitor->SM = &compiler.getSourceManager();
238 /// TestConsumer will be deleted by the framework calling us.
239 return new FindConsumer(Visitor);
240 }
241
242 private:
243 TestVisitor *Visitor;
244 };
245};
246} // end namespace
247
248void expectReplacementAt(const Replacement &Replace,
249 StringRef File, unsigned Offset, unsigned Length) {
250 ASSERT_TRUE(Replace.isApplicable());
251 EXPECT_EQ(File, Replace.getFilePath());
252 EXPECT_EQ(Offset, Replace.getOffset());
253 EXPECT_EQ(Length, Replace.getLength());
254}
255
256class ClassDeclXVisitor : public TestVisitor<ClassDeclXVisitor> {
257public:
258 bool VisitCXXRecordDecl(CXXRecordDecl *Record) {
259 if (Record->getName() == "X") {
260 Replace = Replacement(*SM, Record, "");
261 }
262 return true;
263 }
264 Replacement Replace;
265};
266
267TEST(Replacement, CanBeConstructedFromNode) {
268 ClassDeclXVisitor ClassDeclX;
269 EXPECT_TRUE(ClassDeclX.runOver(" class X;"));
270 expectReplacementAt(ClassDeclX.Replace, "input.cc", 5, 7);
271}
272
273TEST(Replacement, ReplacesAtSpellingLocation) {
274 ClassDeclXVisitor ClassDeclX;
275 EXPECT_TRUE(ClassDeclX.runOver("#define A(Y) Y\nA(class X);"));
276 expectReplacementAt(ClassDeclX.Replace, "input.cc", 17, 7);
277}
278
279class CallToFVisitor : public TestVisitor<CallToFVisitor> {
280public:
281 bool VisitCallExpr(CallExpr *Call) {
282 if (Call->getDirectCallee()->getName() == "F") {
283 Replace = Replacement(*SM, Call, "");
284 }
285 return true;
286 }
287 Replacement Replace;
288};
289
290TEST(Replacement, FunctionCall) {
291 CallToFVisitor CallToF;
292 EXPECT_TRUE(CallToF.runOver("void F(); void G() { F(); }"));
293 expectReplacementAt(CallToF.Replace, "input.cc", 21, 3);
294}
295
296TEST(Replacement, TemplatedFunctionCall) {
297 CallToFVisitor CallToF;
298 EXPECT_TRUE(CallToF.runOver(
299 "template <typename T> void F(); void G() { F<int>(); }"));
300 expectReplacementAt(CallToF.Replace, "input.cc", 43, 8);
301}
302
303} // end namespace tooling
304} // end namespace clang