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