blob: 02b816d5235c30a6b6756373c7e2d39bd4e2857c [file] [log] [blame]
Alex Lorenzb54ef6a2017-09-14 10:06:52 +00001//===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
Alex Lorenzb54ef6a2017-09-14 10:06:52 +00006//
7//===----------------------------------------------------------------------===//
8///
9/// \file
Adrian Prantl9fc8faf2018-05-09 01:00:01 +000010/// This file implements routines that provide refactoring testing
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000011/// utilities.
12///
13//===----------------------------------------------------------------------===//
14
15#include "TestSupport.h"
Alex Lorenzf5ca27c2017-10-16 18:28:26 +000016#include "clang/Basic/DiagnosticError.h"
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000017#include "clang/Basic/SourceManager.h"
18#include "clang/Lex/Lexer.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/Support/Error.h"
21#include "llvm/Support/ErrorOr.h"
Alex Lorenz7fe441b2017-10-24 17:18:45 +000022#include "llvm/Support/LineIterator.h"
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000023#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 {
Harlan Haskins8d323d12019-08-01 21:31:56 +000044 auto FE = SM.getFileManager().getFile(Filename);
45 FileID FID = FE ? SM.translateFile(*FE) : FileID();
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000046 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;
Mark de Wever8dc7b982020-01-01 17:23:21 +010077 for (auto I : llvm::zip(LHS, RHS)) {
Alex Lorenzb54ef6a2017-09-14 10:06:52 +000078 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);
Jonas Devlieghere509e21a2020-01-29 21:27:46 -0800193 ErrorMessage = std::string(DiagText);
Alex Lorenzf5ca27c2017-10-16 18:28:26 +0000194 });
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) {
Alex Lorenz7fe441b2017-10-24 17:18:45 +0000244 llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000245 << "' results:\n";
Alex Lorenz7fe441b2017-10-24 17:18:45 +0000246 llvm::outs() << *CanonicalErrorMessage << "\n";
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000247 } 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 {
Jonas Devlieghere2b3d49b2019-08-14 23:04:18 +0000259 return std::make_unique<TestRefactoringResultConsumer>(*this);
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000260}
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
Alex Lorenz7fe441b2017-10-24 17:18:45 +0000274static unsigned addEndLineOffsetAndEndColumn(StringRef Source, unsigned Offset,
275 unsigned LineNumberOffset,
276 unsigned Column) {
277 StringRef Line = Source.drop_front(Offset);
278 unsigned LineOffset = 0;
279 for (; LineNumberOffset != 0; --LineNumberOffset) {
280 size_t NewlinePos = Line.find_first_of("\r\n");
281 // Line offset goes out of bounds.
282 if (NewlinePos == StringRef::npos)
283 break;
284 LineOffset += NewlinePos + 1;
285 Line = Line.drop_front(NewlinePos + 1);
286 }
287 // Source now points to the line at +lineOffset;
288 size_t LineStart = Source.find_last_of("\r\n", /*From=*/Offset + LineOffset);
289 return addColumnOffset(
290 Source, LineStart == StringRef::npos ? 0 : LineStart + 1, Column - 1);
291}
292
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000293Optional<TestSelectionRangesInFile>
294findTestSelectionRanges(StringRef Filename) {
295 ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
296 MemoryBuffer::getFile(Filename);
297 if (!ErrOrFile) {
298 llvm::errs() << "error: -selection=test:" << Filename
299 << " : could not open the given file";
300 return None;
301 }
302 StringRef Source = ErrOrFile.get()->getBuffer();
303
Alex Lorenzb54ef6a2017-09-14 10:06:52 +0000304 // See the doc comment for this function for the explanation of this
305 // syntax.
Thomas Preud'hommeb81cc602019-11-19 11:18:51 +0000306 static const Regex RangeRegex(
307 "range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:"
308 "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()) {
Thomas Preud'hommeb81cc602019-11-19 11:18:51 +0000356 static const Regex EndLocRegex(
Alex Lorenz7fe441b2017-10-24 17:18:45 +0000357 "->[[: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