blob: 2f127bd7f6d8f809dd60640f9d274a3bbb705bc9 [file] [log] [blame]
Alex Lorenzb54ef6a2017-09-14 10:06:52 +00001//===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
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/// \file
11/// \brief This file implements routines that provide refactoring testing
12/// utilities.
13///
14//===----------------------------------------------------------------------===//
15
16#include "TestSupport.h"
Alex Lorenzf5ca27c2017-10-16 18:28:26 +000017#include "clang/Basic/DiagnosticError.h"
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000018#include "clang/Basic/SourceManager.h"
19#include "clang/Lex/Lexer.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/Support/Error.h"
22#include "llvm/Support/ErrorOr.h"
23#include "llvm/Support/MemoryBuffer.h"
24#include "llvm/Support/Regex.h"
25#include "llvm/Support/raw_ostream.h"
26
27using namespace llvm;
28
29namespace clang {
30namespace refactor {
31
32void TestSelectionRangesInFile::dump(raw_ostream &OS) const {
33 for (const auto &Group : GroupedRanges) {
34 OS << "Test selection group '" << Group.Name << "':\n";
35 for (const auto &Range : Group.Ranges) {
36 OS << " " << Range.Begin << "-" << Range.End << "\n";
37 }
38 }
39}
40
41bool TestSelectionRangesInFile::foreachRange(
42 const SourceManager &SM,
43 llvm::function_ref<void(SourceRange)> Callback) const {
44 const FileEntry *FE = SM.getFileManager().getFile(Filename);
45 FileID FID = FE ? SM.translateFile(FE) : FileID();
46 if (!FE || FID.isInvalid()) {
47 llvm::errs() << "error: -selection=test:" << Filename
48 << " : given file is not in the target TU";
49 return true;
50 }
51 SourceLocation FileLoc = SM.getLocForStartOfFile(FID);
52 for (const auto &Group : GroupedRanges) {
53 for (const TestSelectionRange &Range : Group.Ranges) {
54 // Translate the offset pair to a true source range.
55 SourceLocation Start =
56 SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin));
57 SourceLocation End =
58 SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End));
59 assert(Start.isValid() && End.isValid() && "unexpected invalid range");
60 Callback(SourceRange(Start, End));
61 }
62 }
63 return false;
64}
65
66namespace {
67
68void dumpChanges(const tooling::AtomicChanges &Changes, raw_ostream &OS) {
69 for (const auto &Change : Changes)
70 OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n";
71}
72
73bool areChangesSame(const tooling::AtomicChanges &LHS,
74 const tooling::AtomicChanges &RHS) {
75 if (LHS.size() != RHS.size())
76 return false;
77 for (const auto &I : llvm::zip(LHS, RHS)) {
78 if (!(std::get<0>(I) == std::get<1>(I)))
79 return false;
80 }
81 return true;
82}
83
84bool printRewrittenSources(const tooling::AtomicChanges &Changes,
85 raw_ostream &OS) {
86 std::set<std::string> Files;
87 for (const auto &Change : Changes)
88 Files.insert(Change.getFilePath());
89 tooling::ApplyChangesSpec Spec;
90 Spec.Cleanup = false;
91 for (const auto &File : Files) {
92 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr =
93 llvm::MemoryBuffer::getFile(File);
94 if (!BufferErr) {
95 llvm::errs() << "failed to open" << File << "\n";
96 return true;
97 }
98 auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(),
99 Changes, Spec);
100 if (!Result) {
101 llvm::errs() << toString(Result.takeError());
102 return true;
103 }
104 OS << *Result;
105 }
106 return false;
107}
108
109class TestRefactoringResultConsumer final
Alex Lorenzf5ca27c2017-10-16 18:28:26 +0000110 : public ClangRefactorToolConsumerInterface {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000111public:
112 TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges)
113 : TestRanges(TestRanges) {
114 Results.push_back({});
115 }
116
117 ~TestRefactoringResultConsumer() {
118 // Ensure all results are checked.
119 for (auto &Group : Results) {
120 for (auto &Result : Group) {
121 if (!Result) {
122 (void)llvm::toString(Result.takeError());
123 }
124 }
125 }
126 }
127
128 void handleError(llvm::Error Err) override { handleResult(std::move(Err)); }
129
130 void handle(tooling::AtomicChanges Changes) override {
131 handleResult(std::move(Changes));
132 }
133
134 void handle(tooling::SymbolOccurrences Occurrences) override {
135 tooling::RefactoringResultConsumer::handle(std::move(Occurrences));
136 }
137
138private:
139 bool handleAllResults();
140
141 void handleResult(Expected<tooling::AtomicChanges> Result) {
142 Results.back().push_back(std::move(Result));
143 size_t GroupIndex = Results.size() - 1;
144 if (Results.back().size() >=
145 TestRanges.GroupedRanges[GroupIndex].Ranges.size()) {
146 ++GroupIndex;
147 if (GroupIndex >= TestRanges.GroupedRanges.size()) {
148 if (handleAllResults())
149 exit(1); // error has occurred.
150 return;
151 }
152 Results.push_back({});
153 }
154 }
155
156 const TestSelectionRangesInFile &TestRanges;
157 std::vector<std::vector<Expected<tooling::AtomicChanges>>> Results;
158};
159
160std::pair<unsigned, unsigned> getLineColumn(StringRef Filename,
161 unsigned Offset) {
162 ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
163 MemoryBuffer::getFile(Filename);
164 if (!ErrOrFile)
165 return {0, 0};
166 StringRef Source = ErrOrFile.get()->getBuffer();
167 Source = Source.take_front(Offset);
168 size_t LastLine = Source.find_last_of("\r\n");
169 return {Source.count('\n') + 1,
170 (LastLine == StringRef::npos ? Offset : Offset - LastLine) + 1};
171}
172
173} // end anonymous namespace
174
175bool TestRefactoringResultConsumer::handleAllResults() {
176 bool Failed = false;
177 for (auto &Group : llvm::enumerate(Results)) {
178 // All ranges in the group must produce the same result.
179 Optional<tooling::AtomicChanges> CanonicalResult;
180 Optional<std::string> CanonicalErrorMessage;
181 for (auto &I : llvm::enumerate(Group.value())) {
182 Expected<tooling::AtomicChanges> &Result = I.value();
183 std::string ErrorMessage;
184 bool HasResult = !!Result;
185 if (!HasResult) {
Alex Lorenzf5ca27c2017-10-16 18:28:26 +0000186 handleAllErrors(
187 Result.takeError(),
188 [&](StringError &Err) { ErrorMessage = Err.getMessage(); },
189 [&](DiagnosticError &Err) {
190 const PartialDiagnosticAt &Diag = Err.getDiagnostic();
191 llvm::SmallString<100> DiagText;
192 Diag.second.EmitToString(getDiags(), DiagText);
193 ErrorMessage = DiagText.str().str();
194 });
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000195 }
196 if (!CanonicalResult && !CanonicalErrorMessage) {
197 if (HasResult)
198 CanonicalResult = std::move(*Result);
199 else
200 CanonicalErrorMessage = std::move(ErrorMessage);
201 continue;
202 }
203
204 // Verify that this result corresponds to the canonical result.
205 if (CanonicalErrorMessage) {
206 // The error messages must match.
207 if (!HasResult && ErrorMessage == *CanonicalErrorMessage)
208 continue;
209 } else {
210 assert(CanonicalResult && "missing canonical result");
211 // The results must match.
212 if (HasResult && areChangesSame(*Result, *CanonicalResult))
213 continue;
214 }
215 Failed = true;
216 // Report the mismatch.
217 std::pair<unsigned, unsigned> LineColumn = getLineColumn(
218 TestRanges.Filename,
219 TestRanges.GroupedRanges[Group.index()].Ranges[I.index()].Begin);
220 llvm::errs()
221 << "error: unexpected refactoring result for range starting at "
222 << LineColumn.first << ':' << LineColumn.second << " in group '"
223 << TestRanges.GroupedRanges[Group.index()].Name << "':\n ";
224 if (HasResult)
225 llvm::errs() << "valid result";
226 else
227 llvm::errs() << "error '" << ErrorMessage << "'";
228 llvm::errs() << " does not match initial ";
229 if (CanonicalErrorMessage)
230 llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n";
231 else
232 llvm::errs() << "valid result\n";
233 if (HasResult && !CanonicalErrorMessage) {
234 llvm::errs() << " Expected to Produce:\n";
235 dumpChanges(*CanonicalResult, llvm::errs());
236 llvm::errs() << " Produced:\n";
237 dumpChanges(*Result, llvm::errs());
238 }
239 }
240
241 // Dump the results:
242 const auto &TestGroup = TestRanges.GroupedRanges[Group.index()];
243 if (!CanonicalResult) {
244 llvm::errs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
245 << "' results:\n";
246 llvm::errs() << *CanonicalErrorMessage << "\n";
247 } else {
248 llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
249 << "' results:\n";
250 if (printRewrittenSources(*CanonicalResult, llvm::outs()))
251 return true;
252 }
253 }
254 return Failed;
255}
256
Alex Lorenzf5ca27c2017-10-16 18:28:26 +0000257std::unique_ptr<ClangRefactorToolConsumerInterface>
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000258TestSelectionRangesInFile::createConsumer() const {
259 return llvm::make_unique<TestRefactoringResultConsumer>(*this);
260}
261
262/// Adds the \p ColumnOffset to file offset \p Offset, without going past a
263/// newline.
264static unsigned addColumnOffset(StringRef Source, unsigned Offset,
265 unsigned ColumnOffset) {
266 if (!ColumnOffset)
267 return Offset;
268 StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset);
269 size_t NewlinePos = Substr.find_first_of("\r\n");
270 return Offset +
271 (NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos);
272}
273
274Optional<TestSelectionRangesInFile>
275findTestSelectionRanges(StringRef Filename) {
276 ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
277 MemoryBuffer::getFile(Filename);
278 if (!ErrOrFile) {
279 llvm::errs() << "error: -selection=test:" << Filename
280 << " : could not open the given file";
281 return None;
282 }
283 StringRef Source = ErrOrFile.get()->getBuffer();
284
285 // FIXME (Alex L): 3rd capture groups for +line:column.
286 // See the doc comment for this function for the explanation of this
287 // syntax.
288 static Regex RangeRegex("range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
289 "blank:]]*(\\+[[:digit:]]+)?");
290
291 std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges;
292
293 LangOptions LangOpts;
294 LangOpts.CPlusPlus = 1;
295 LangOpts.CPlusPlus11 = 1;
296 Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(),
297 Source.begin(), Source.end());
298 Lex.SetCommentRetentionState(true);
299 Token Tok;
300 for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof);
301 Lex.LexFromRawLexer(Tok)) {
302 if (Tok.isNot(tok::comment))
303 continue;
304 StringRef Comment =
305 Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength());
306 SmallVector<StringRef, 4> Matches;
307 // Allow CHECK: comments to contain range= commands.
308 if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) {
309 // Try to detect mistyped 'range:' comments to ensure tests don't miss
310 // anything.
311 if (Comment.contains_lower("range") && Comment.contains("=") &&
312 !Comment.contains_lower("run") && !Comment.contains("CHECK")) {
313 llvm::errs() << "error: suspicious comment '" << Comment
314 << "' that "
315 "resembles the range command found\n";
316 llvm::errs() << "note: please reword if this isn't a range command\n";
317 return None;
318 }
319 continue;
320 }
321 unsigned Offset = Tok.getEndLoc().getRawEncoding();
322 unsigned ColumnOffset = 0;
323 if (!Matches[2].empty()) {
324 // Don't forget to drop the '+'!
325 if (Matches[2].drop_front().getAsInteger(10, ColumnOffset))
326 assert(false && "regex should have produced a number");
327 }
328 // FIXME (Alex L): Support true ranges.
329 Offset = addColumnOffset(Source, Offset, ColumnOffset);
330 TestSelectionRange Range = {Offset, Offset};
331 auto It = GroupedRanges.insert(std::make_pair(
332 Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range}));
333 if (!It.second)
334 It.first->second.push_back(Range);
335 }
336 if (GroupedRanges.empty()) {
337 llvm::errs() << "error: -selection=test:" << Filename
338 << ": no 'range' commands";
339 return None;
340 }
341
342 TestSelectionRangesInFile TestRanges = {Filename.str(), {}};
343 for (auto &Group : GroupedRanges)
344 TestRanges.GroupedRanges.push_back({Group.first, std::move(Group.second)});
345 return std::move(TestRanges);
346}
347
348} // end namespace refactor
349} // end namespace clang