[FileCheck] Add a diff output option for FileCheck (#187120)
This patch adds a `--diff` flag to FileCheck to address the readability
of traditional FileCheck output which can be difficult to parse by
human, especially when dealing with multiple substitutions or large
input files with many mismatches and additional context. This feature
provides a more familiar, scannable format for developers by rendering
mismatches as diffs.
There are two diff modes, split and unified both with substitution and
no-substitution version however to make it easier for reviewer, this
only have unified with no substitution.
Functional description of PR-
`getDiffContext` -
It provides the surrounding context for the mismatch. It uses the
`SourceMgr` to find the Line using `LineNo`. Once it found the pointer,
it scans forward until it hits a newline (\n or \r) or the end of the
memory buffer, then it trim to remove the whitespaces.
`renderDiff`
This function handle the printing of diff, print header(expected and
actual line number), one line context before the target line, mismatch
i.e. expected line and actual line and finally one line after context.
`printDiff`
This does the preparation for printing the diff. It has `CheckStr` with
which it find the expected (pattern) line, It resolve the actual line
either with `OverwriteActualLine` (which it gets from handleDiffFailure
which uses fuzzy match `diag` vector) or with directly `SourceMgr`. It
then asked for Line context from `getDiffContext` and finally call
`renderDiff` for actual printing.
`handleDiffFailure`
This function is important. it first print header of input file and
pattern file path. Then do main task of finding the target input line
for the check pattern line. It uses fuzzy match to find most intended
match. If there is none then target line default to next input line in
the buffer. It then move the `CheckRegion` for next Check line.
`checkInput`
This is where we intercept the flow for diff output. checkInput function
traverse on each check line of each check label. It find the MatchPos,
if it is `npos` then break the loop for check label but in our case we
also call `handleDiffFailure` for printing mismatch. We also have a
handling of check-next and check-empty where even if `MatchPos` is > 0
i.e. there is match later, we intercept then call `handleDiffFailure`
for printing the gap as mismatch.
Example -
`$ cat input.ll`
```llvm
define i32 @sum() {
%res = add nsw i32 10, 20
ret i64 %res
}
```
`$ cat test.txt`
```llvm
; CHECK-LABEL: define i32 @calculate()
; CHECK: %res = add nsw i32 20, 10
; CHECK-NEXT: ret i32 %res
```
`$bin/FileCheck ./test.txt --input-file=./input.ll --diff=unidiff`
```llvm
--- ./test.txt
+++ ./input.ll
@@ -2 +2 @@
define i32 @cal() {
-%res = add nsw i32 20, 10
+%res = add nsw i32 10, 20
ret i64 %res
FileCheck: Found 1 unique textual mismatch.
```
Fixes #77257
Assisted by Gemini AI.
This commit is contained in:
@@ -110,6 +110,14 @@ and from the command line.
|
||||
-verify``. With this option FileCheck will verify that input does not contain
|
||||
warnings not covered by any ``CHECK:`` patterns.
|
||||
|
||||
.. option:: --diff <value>
|
||||
|
||||
Controls how mismatches between the check patterns and the input are
|
||||
reported. If ``--diff`` is specified without a value, it defaults to ``unidiff``.
|
||||
|
||||
* ``none`` – Use the standard FileCheck diagnostic messages.
|
||||
* ``unidiff`` – Display mismatches using a unified diff format.
|
||||
|
||||
.. option:: --dump-input <value>
|
||||
|
||||
Dump input to stderr, adding annotations representing currently enabled
|
||||
|
||||
@@ -27,6 +27,9 @@ class MemoryBuffer;
|
||||
class SourceMgr;
|
||||
template <typename T> class SmallVectorImpl;
|
||||
|
||||
// Diff the output on failures.
|
||||
enum DiffFormatType { None, Unified };
|
||||
|
||||
/// Contains info about various FileCheck options.
|
||||
struct FileCheckRequest {
|
||||
std::vector<StringRef> CheckPrefixes;
|
||||
@@ -43,6 +46,7 @@ struct FileCheckRequest {
|
||||
bool AllowDeprecatedDagOverlap = false;
|
||||
bool Verbose = false;
|
||||
bool VerboseVerbose = false;
|
||||
DiffFormatType DiffFormat = DiffFormatType::None;
|
||||
};
|
||||
|
||||
namespace Check {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/ADT/Twine.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/WithColor.h"
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <set>
|
||||
@@ -1261,7 +1262,8 @@ unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
|
||||
void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
|
||||
SMRange Range,
|
||||
FileCheckDiag::MatchType MatchTy,
|
||||
std::vector<FileCheckDiag> *Diags) const {
|
||||
std::vector<FileCheckDiag> *Diags,
|
||||
const FileCheckRequest &Req) const {
|
||||
// Print what we know about substitutions.
|
||||
if (!Substitutions.empty()) {
|
||||
for (const auto &Substitution : Substitutions) {
|
||||
@@ -1287,8 +1289,9 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
|
||||
if (Diags)
|
||||
Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy,
|
||||
SMRange(Range.Start, Range.Start), OS.str());
|
||||
else
|
||||
else if (Req.DiffFormat == DiffFormatType::None) {
|
||||
SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1368,7 +1371,8 @@ static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy,
|
||||
}
|
||||
|
||||
void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
|
||||
std::vector<FileCheckDiag> *Diags) const {
|
||||
std::vector<FileCheckDiag> *Diags,
|
||||
const FileCheckRequest &Req) const {
|
||||
// Attempt to find the closest/best fuzzy match. Usually an error happens
|
||||
// because some string in the output didn't exactly match. In these cases, we
|
||||
// would like to show the user a best guess at what "should have" matched, to
|
||||
@@ -1411,8 +1415,9 @@ void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
|
||||
SMRange MatchRange =
|
||||
ProcessMatchResult(FileCheckDiag::MatchFuzzy, SM, getLoc(),
|
||||
getCheckTy(), Buffer, Best, 0, Diags);
|
||||
SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
|
||||
"possible intended match here");
|
||||
if (Req.DiffFormat == DiffFormatType::None)
|
||||
SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
|
||||
"possible intended match here");
|
||||
|
||||
// FIXME: If we wanted to be really friendly we would show why the match
|
||||
// failed, as it can be hard to spot simple one character differences.
|
||||
@@ -2057,7 +2062,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
|
||||
Buffer, MatchResult.TheMatch->Pos,
|
||||
MatchResult.TheMatch->Len, Diags);
|
||||
if (Diags) {
|
||||
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
|
||||
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags, Req);
|
||||
Pat.printVariableDefs(SM, MatchTy, Diags);
|
||||
}
|
||||
if (!PrintDiag) {
|
||||
@@ -2078,7 +2083,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
|
||||
{MatchRange});
|
||||
|
||||
// Print additional information, which can be useful even if there are errors.
|
||||
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr);
|
||||
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr, Req);
|
||||
Pat.printVariableDefs(SM, MatchTy, nullptr);
|
||||
|
||||
// Print errors and add them to Diags. We report these errors after the match
|
||||
@@ -2102,7 +2107,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
|
||||
static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
|
||||
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
|
||||
int MatchedCount, StringRef Buffer, Error MatchError,
|
||||
bool VerboseVerbose,
|
||||
bool VerboseVerbose, const FileCheckRequest &Req,
|
||||
std::vector<FileCheckDiag> *Diags) {
|
||||
// Print any pattern errors, and record them to be added to Diags later.
|
||||
bool HasError = ExpectedMatch;
|
||||
@@ -2148,7 +2153,7 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
|
||||
for (StringRef ErrorMsg : ErrorMsgs)
|
||||
Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange,
|
||||
ErrorMsg);
|
||||
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
|
||||
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags, Req);
|
||||
}
|
||||
if (!PrintDiag) {
|
||||
assert(!HasError && "expected to report more diagnostics for error");
|
||||
@@ -2165,18 +2170,20 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
|
||||
if (Pat.getCount() > 1)
|
||||
Message +=
|
||||
formatv(" ({0} out of {1})", MatchedCount, Pat.getCount()).str();
|
||||
SM.PrintMessage(Loc,
|
||||
ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark,
|
||||
Message);
|
||||
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note,
|
||||
"scanning from here");
|
||||
if (Req.DiffFormat == DiffFormatType::None) {
|
||||
SM.PrintMessage(
|
||||
Loc, ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark,
|
||||
Message);
|
||||
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note,
|
||||
"scanning from here");
|
||||
}
|
||||
}
|
||||
|
||||
// Print additional information, which can be useful even after a pattern
|
||||
// error.
|
||||
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr);
|
||||
Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr, Req);
|
||||
if (ExpectedMatch)
|
||||
Pat.printFuzzyMatch(SM, Buffer, Diags);
|
||||
Pat.printFuzzyMatch(SM, Buffer, Diags, Req);
|
||||
return ErrorReported::reportedOrSuccess(HasError);
|
||||
}
|
||||
|
||||
@@ -2192,7 +2199,7 @@ static Error reportMatchResult(bool ExpectedMatch, const SourceMgr &SM,
|
||||
return printMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
|
||||
std::move(MatchResult), Req, Diags);
|
||||
return printNoMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
|
||||
std::move(MatchResult.TheError), Req.VerboseVerbose,
|
||||
std::move(MatchResult.TheError), Req.VerboseVerbose, Req,
|
||||
Diags);
|
||||
}
|
||||
|
||||
@@ -2275,7 +2282,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
|
||||
|
||||
// If this check is a "CHECK-NEXT", verify that the previous match was on
|
||||
// the previous line (i.e. that there is one newline between them).
|
||||
if (CheckNext(SM, SkippedRegion)) {
|
||||
if (CheckNext(SM, SkippedRegion, Req)) {
|
||||
ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc,
|
||||
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
|
||||
Diags, Req.Verbose);
|
||||
@@ -2300,7 +2307,8 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
|
||||
return FirstMatchPos;
|
||||
}
|
||||
|
||||
bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const {
|
||||
bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer,
|
||||
const FileCheckRequest &Req) const {
|
||||
if (Pat.getCheckTy() != Check::CheckNext &&
|
||||
Pat.getCheckTy() != Check::CheckEmpty)
|
||||
return false;
|
||||
@@ -2312,8 +2320,7 @@ bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const {
|
||||
// Count the number of newlines between the previous match and this one.
|
||||
const char *FirstNewLine = nullptr;
|
||||
unsigned NumNewLines = CountNumNewlinesBetween(Buffer, FirstNewLine);
|
||||
|
||||
if (NumNewLines == 0) {
|
||||
if (NumNewLines == 0 && Req.DiffFormat == DiffFormatType::None) {
|
||||
SM.PrintMessage(Loc, SourceMgr::DK_Error,
|
||||
CheckName + ": is on the same line as previous match");
|
||||
SM.PrintMessage(SMLoc::getFromPointer(Buffer.end()), SourceMgr::DK_Note,
|
||||
@@ -2323,7 +2330,7 @@ bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NumNewLines != 1) {
|
||||
if (NumNewLines != 1 && Req.DiffFormat == DiffFormatType::None) {
|
||||
SM.PrintMessage(Loc, SourceMgr::DK_Error,
|
||||
CheckName +
|
||||
": is not on the line after the previous match");
|
||||
@@ -2724,9 +2731,168 @@ void FileCheckPatternContext::clearLocalVars() {
|
||||
GlobalNumericVariableTable.erase(Var);
|
||||
}
|
||||
|
||||
struct DiffContext {
|
||||
StringRef Line;
|
||||
StringRef LineBefore;
|
||||
StringRef LineAfter;
|
||||
};
|
||||
|
||||
// Provides the "surrounding context" for diff output.
|
||||
static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
|
||||
unsigned BufID) {
|
||||
const MemoryBuffer *Buffer = SM.getMemoryBuffer(BufID);
|
||||
StringRef BufText = Buffer->getBuffer();
|
||||
|
||||
auto getLineText = [&](unsigned L) -> StringRef {
|
||||
if (L == 0)
|
||||
return "";
|
||||
|
||||
SMLoc LineLoc = SM.FindLocForLineAndColumn(BufID, L, 1);
|
||||
if (!LineLoc.isValid())
|
||||
return "";
|
||||
|
||||
StringRef FromLineStart(LineLoc.getPointer(),
|
||||
BufText.end() - LineLoc.getPointer());
|
||||
return FromLineStart
|
||||
.take_while([](char C) { return C != '\n' && C != '\r'; })
|
||||
.trim();
|
||||
};
|
||||
|
||||
return {getLineText(LineNo), getLineText(LineNo - 1),
|
||||
getLineText(LineNo + 1)};
|
||||
}
|
||||
|
||||
// Renders a diagnostic diff via llvm::errs().
|
||||
static void renderDiff(unsigned ExpectedLineNo, unsigned ActualLineNo,
|
||||
StringRef ExpectedLine, StringRef ActualLine,
|
||||
const DiffContext &Ctx) {
|
||||
auto &OS = errs();
|
||||
|
||||
// Header
|
||||
OS.changeColor(raw_ostream::CYAN);
|
||||
OS << "@@ -" << ExpectedLineNo << " +" << ActualLineNo << " @@\n";
|
||||
OS.resetColor();
|
||||
|
||||
// Before Context
|
||||
if (!Ctx.LineBefore.empty()) {
|
||||
OS << " " << Ctx.LineBefore << "\n";
|
||||
}
|
||||
|
||||
// Mismatch
|
||||
OS.changeColor(raw_ostream::RED);
|
||||
OS << "-" << ExpectedLine << "\n";
|
||||
|
||||
OS.changeColor(raw_ostream::GREEN);
|
||||
OS << "+" << ActualLine.ltrim() << "\n";
|
||||
OS.resetColor();
|
||||
|
||||
// After Context
|
||||
if (!Ctx.LineAfter.empty()) {
|
||||
OS << " " << Ctx.LineAfter << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
|
||||
SourceMgr &SM, std::vector<FileCheckDiag> *Diags,
|
||||
unsigned OverwriteActualLine = 0) {
|
||||
SMLoc PatternLoc = CheckStr.Pat.getLoc();
|
||||
unsigned ExpectedLineNo = SM.getLineAndColumn(PatternLoc).first;
|
||||
const char *PatPtr = PatternLoc.getPointer();
|
||||
StringRef ExpectedLine = StringRef(PatPtr).split('\n').first.rtrim();
|
||||
|
||||
// Resolve the Actual (Input) line number.
|
||||
// Priority: 1. OverwriteActualLine (Found via Fuzzy match)
|
||||
// 2. Direct pointer resolution via SourceMgr.
|
||||
SMLoc InputLoc = SMLoc::getFromPointer(ActualLine.data());
|
||||
|
||||
unsigned ActualLineNo = OverwriteActualLine;
|
||||
|
||||
// If no Fuzzy match was found, calculate the line number directly
|
||||
// from the InputLoc pointer using the SourceManager.
|
||||
if (ActualLineNo == 0)
|
||||
ActualLineNo = SM.getLineAndColumn(InputLoc).first;
|
||||
|
||||
// if we are at an empty line (and not from fuzzy), usually the relevant
|
||||
// context is the line just before it.
|
||||
if (ActualLine.empty() && ActualLineNo > 1)
|
||||
ActualLineNo--;
|
||||
|
||||
unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
|
||||
DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
|
||||
|
||||
renderDiff(ExpectedLineNo, ActualLineNo, ExpectedLine, ActualLine, Context);
|
||||
|
||||
errs() << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
// Report the mismatch on the current line and advance to the next line.
|
||||
static bool handleDiffFailure(const FileCheckString &CheckStr,
|
||||
StringRef &CheckRegion, SourceMgr &SM,
|
||||
std::vector<FileCheckDiag> *Diags,
|
||||
raw_ostream &OS, bool &HeaderPrinted,
|
||||
unsigned &TotalMismatches) {
|
||||
// Print headers once per CheckRegion.
|
||||
if (!HeaderPrinted) {
|
||||
StringRef CheckFile =
|
||||
SM.getMemoryBuffer(SM.getMainFileID())->getBufferIdentifier();
|
||||
unsigned InputBufID =
|
||||
SM.FindBufferContainingLoc(SMLoc::getFromPointer(CheckRegion.data()));
|
||||
StringRef InputFile = SM.getMemoryBuffer(InputBufID)->getBufferIdentifier();
|
||||
|
||||
OS.changeColor(raw_ostream::WHITE, true);
|
||||
OS << "--- " << CheckFile << "\n";
|
||||
OS << "+++ " << InputFile << "\n";
|
||||
OS.resetColor();
|
||||
HeaderPrinted = true;
|
||||
}
|
||||
|
||||
size_t EOL = CheckRegion.find('\n');
|
||||
|
||||
SMLoc CurrentLoc = SMLoc::getFromPointer(CheckRegion.data());
|
||||
StringRef TargetLine;
|
||||
unsigned TargetLineNo = 0;
|
||||
|
||||
// Check if the existing diagnostics already found a fuzzy match.
|
||||
if (Diags) {
|
||||
for (const auto &D : llvm::reverse(*Diags)) {
|
||||
if (D.CheckLoc == CheckStr.Pat.getLoc() &&
|
||||
D.MatchTy == FileCheckDiag::MatchFuzzy) {
|
||||
TargetLineNo = D.InputStartLine;
|
||||
// Get the actual text of that fuzzy match from the SourceMgr
|
||||
unsigned BufID = SM.FindBufferContainingLoc(CurrentLoc);
|
||||
SMLoc FuzzyLoc = SM.FindLocForLineAndColumn(BufID, TargetLineNo, 1);
|
||||
TargetLine = StringRef(FuzzyLoc.getPointer()).split('\n').first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no fuzzy match was found by the engine, just use the next line.
|
||||
if (TargetLine.empty()) {
|
||||
TargetLine = CheckRegion.substr(0, EOL);
|
||||
TargetLineNo = SM.getLineAndColumn(CurrentLoc).first;
|
||||
}
|
||||
|
||||
printDiff(CheckStr, TargetLine, SM, Diags, TargetLineNo);
|
||||
TotalMismatches++;
|
||||
|
||||
// Advance CheckRegion past the current line to recover for the next CHECK.
|
||||
if (EOL != StringRef::npos)
|
||||
CheckRegion = CheckRegion.substr(EOL + 1);
|
||||
else
|
||||
CheckRegion = "";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
|
||||
std::vector<FileCheckDiag> *Diags) {
|
||||
bool ChecksFailed = false;
|
||||
unsigned TotalMismatches = 0;
|
||||
bool HeaderPrinted = false;
|
||||
bool IsDiffFormat = Req.DiffFormat != DiffFormatType::None;
|
||||
auto &OS = errs();
|
||||
|
||||
unsigned i = 0, j = 0, e = CheckStrings.size();
|
||||
while (true) {
|
||||
@@ -2759,16 +2925,40 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
|
||||
if (i != 0 && Req.EnableVarScope)
|
||||
PatternContext->clearLocalVars();
|
||||
|
||||
// Check each string within the scanned region, including a second check
|
||||
// of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
|
||||
for (; i != j; ++i) {
|
||||
const FileCheckString &CheckStr = CheckStrings[i];
|
||||
|
||||
// Check each string within the scanned region, including a second check
|
||||
// of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
|
||||
bool IsStrict = CheckStr.Pat.getCheckTy() == Check::CheckNext ||
|
||||
CheckStr.Pat.getCheckTy() == Check::CheckEmpty;
|
||||
|
||||
size_t MatchLen = 0;
|
||||
size_t MatchPos =
|
||||
CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags);
|
||||
|
||||
// Handle failure
|
||||
if (MatchPos == StringRef::npos) {
|
||||
if (IsDiffFormat) {
|
||||
handleDiffFailure(CheckStr, CheckRegion, SM, Diags, OS, HeaderPrinted,
|
||||
TotalMismatches);
|
||||
}
|
||||
ChecksFailed = true;
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
// In Diff Mode, while doing strick checking even if we found a match
|
||||
// later i.e. MatchPos > 0, we must stop processing this block and print
|
||||
// the gap as mismatch.
|
||||
if (IsDiffFormat && IsStrict && MatchPos > 0) {
|
||||
// Create a temporary view that starts with next new line.
|
||||
size_t CurrentLineEnd = CheckRegion.find_first_of("\n\r");
|
||||
StringRef NextLineRegion =
|
||||
(CurrentLineEnd != StringRef::npos)
|
||||
? CheckRegion.drop_front(CurrentLineEnd + 1)
|
||||
: CheckRegion;
|
||||
handleDiffFailure(CheckStr, NextLineRegion, SM, Diags, OS,
|
||||
HeaderPrinted, TotalMismatches);
|
||||
ChecksFailed = true;
|
||||
i = j;
|
||||
break;
|
||||
@@ -2781,6 +2971,12 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
|
||||
break;
|
||||
}
|
||||
|
||||
if (Req.DiffFormat != DiffFormatType::None && TotalMismatches > 0) {
|
||||
OS.changeColor(llvm::raw_ostream::YELLOW, true);
|
||||
OS << "FileCheck: Found " << TotalMismatches << " unique textual mismatch"
|
||||
<< (TotalMismatches > 1 ? "es." : ".") << "\n";
|
||||
OS.resetColor();
|
||||
}
|
||||
// Success if no checks failed.
|
||||
return !ChecksFailed;
|
||||
}
|
||||
|
||||
@@ -734,9 +734,11 @@ public:
|
||||
/// Prints the value of successful substitutions.
|
||||
void printSubstitutions(const SourceMgr &SM, StringRef Buffer,
|
||||
SMRange MatchRange, FileCheckDiag::MatchType MatchTy,
|
||||
std::vector<FileCheckDiag> *Diags) const;
|
||||
std::vector<FileCheckDiag> *Diags,
|
||||
const FileCheckRequest &Req) const;
|
||||
void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
|
||||
std::vector<FileCheckDiag> *Diags) const;
|
||||
std::vector<FileCheckDiag> *Diags,
|
||||
const FileCheckRequest &Req) const;
|
||||
|
||||
bool hasVariable() const {
|
||||
return !(Substitutions.empty() && VariableDefs.empty());
|
||||
@@ -874,7 +876,8 @@ struct FileCheckString {
|
||||
|
||||
/// Verifies that there is a single line in the given \p Buffer. Errors are
|
||||
/// reported against \p SM.
|
||||
bool CheckNext(const SourceMgr &SM, StringRef Buffer) const;
|
||||
bool CheckNext(const SourceMgr &SM, StringRef Buffer,
|
||||
const FileCheckRequest &Req) const;
|
||||
/// Verifies that there is no newline in the given \p Buffer. Errors are
|
||||
/// reported against \p SM.
|
||||
bool CheckSame(const SourceMgr &SM, StringRef Buffer) const;
|
||||
|
||||
18
llvm/test/FileCheck/diff/diff-blank-stray.txt
Normal file
18
llvm/test/FileCheck/diff/diff-blank-stray.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
LINE1
|
||||
|
||||
|
||||
|
||||
LINE2
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: LINE1
|
||||
; CHECK-NEXT: LINE2
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: -LINE2
|
||||
; DIFF-NEXT: +
|
||||
19
llvm/test/FileCheck/diff/diff-empty-check.txt
Normal file
19
llvm/test/FileCheck/diff/diff-empty-check.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
START_TRANS
|
||||
[LOG] Transaction initiated...
|
||||
END_TRANS
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: START_TRANS
|
||||
; CHECK-EMPTY:
|
||||
; CHECK: END_TRANS
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: START_TRANS
|
||||
; DIFF-NEXT: -
|
||||
; DIFF-NEXT: +[LOG] Transaction initiated...
|
||||
; DIFF-NEXT: END_TRANS
|
||||
20
llvm/test/FileCheck/diff/diff-label-boundary.txt
Normal file
20
llvm/test/FileCheck/diff/diff-label-boundary.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
FUNCTION_A
|
||||
BAD_OP
|
||||
FUNCTION_B
|
||||
GOOD_OP
|
||||
|
||||
;--- check.txt
|
||||
; CHECK-LABEL: FUNCTION_A
|
||||
; CHECK: CORRECT_OP
|
||||
; CHECK-LABEL: FUNCTION_B
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: FUNCTION_A
|
||||
; DIFF-NEXT: -CORRECT_OP
|
||||
; DIFF-NEXT: +BAD_OP
|
||||
; DIFF-NEXT: FUNCTION_B
|
||||
18
llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
Normal file
18
llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
define void @foo() {
|
||||
call void @work_task()
|
||||
}
|
||||
|
||||
;--- check.txt
|
||||
; CHECK-LABEL: define void @foo()
|
||||
; CHECK: call void @work_test()
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF: define void @foo() {
|
||||
; DIFF-NEXT: -call void @work_test()
|
||||
; DIFF-NEXT: +call void @work_task()
|
||||
; DIFF-NEXT: }
|
||||
19
llvm/test/FileCheck/diff/diff-label-next.txt
Normal file
19
llvm/test/FileCheck/diff/diff-label-next.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
FUNC_A
|
||||
mov r0, #1
|
||||
FUNC_B
|
||||
mov r1, #2
|
||||
|
||||
;--- check.txt
|
||||
; CHECK-LABEL: FUNC_A
|
||||
; CHECK-NEXT: mov r0, #5
|
||||
; CHECK-LABEL: FUNC_B
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: FUNC_A
|
||||
; DIFF-NEXT: -mov r0, #5
|
||||
; DIFF-NEXT: +mov r0, #1
|
||||
32
llvm/test/FileCheck/diff/diff-multi-block.txt
Normal file
32
llvm/test/FileCheck/diff/diff-multi-block.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
define void @func_a() {
|
||||
%result = add i32 %1, %2
|
||||
ret void
|
||||
}
|
||||
define void @func_b() {
|
||||
%val = load i32, ptr %p
|
||||
store i32 %val, ptr %q
|
||||
ret void
|
||||
}
|
||||
|
||||
;--- check.txt
|
||||
; CHECK-LABEL: define void @func_a()
|
||||
; CHECK-NEXT: %result = mul i32 %1, %2
|
||||
; CHECK-LABEL: define void @func_b()
|
||||
; CHECK-NEXT: store i32 %val, ptr %q
|
||||
|
||||
; DIFF: --- {{.*}}check.txt
|
||||
; DIFF-NEXT: +++ {{.*}}input.txt
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: define void @func_a() {
|
||||
; DIFF-NEXT: -%result = mul i32 %1, %2
|
||||
; DIFF-NEXT: +%result = add i32 %1, %2
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: define void @func_b() {
|
||||
; DIFF-NEXT: -store i32 %val, ptr %q
|
||||
; DIFF-NEXT: +%val = load i32, ptr %p
|
||||
; DIFF: FileCheck: Found 2 unique textual mismatches.
|
||||
25
llvm/test/FileCheck/diff/diff-multi-failres.txt
Normal file
25
llvm/test/FileCheck/diff/diff-multi-failres.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
LINE1
|
||||
BAD_LINE
|
||||
LINE3
|
||||
LINE4
|
||||
BAD_LINE
|
||||
LINE6
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: LINE1
|
||||
; CHECK: LINE2
|
||||
; CHECK: LINE3
|
||||
; CHECK: LINE4
|
||||
; CHECK: LINE5
|
||||
; CHECK: LINE6
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: LINE1
|
||||
; DIFF-NEXT: -LINE2
|
||||
; DIFF-NEXT: +BAD_LINE
|
||||
; DIFF-NEXT: LINE3
|
||||
36
llvm/test/FileCheck/diff/diff-multi-mismatch.txt
Normal file
36
llvm/test/FileCheck/diff/diff-multi-mismatch.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
BLOCK1_FAIL
|
||||
MATCH 1
|
||||
MATCH 2
|
||||
MATCH 3
|
||||
MATCH 4
|
||||
MATCH 5
|
||||
MATCH 6
|
||||
MATCH 7
|
||||
BLOCK2_FAIL
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: BLOCK1_PASS
|
||||
; CHECK: MATCH 1
|
||||
; CHECK: MATCH 2
|
||||
; CHECK: MATCH 3
|
||||
; CHECK: MATCH 4
|
||||
; CHECK: MATCH 5
|
||||
; CHECK: MATCH 6
|
||||
; CHECK: MATCH 7
|
||||
; CHECK: BLOCK2_PASS
|
||||
|
||||
; First Hunk
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: -BLOCK1_PASS
|
||||
; DIFF-NEXT: +BLOCK1_FAIL
|
||||
; DIFF-NEXT: MATCH 1
|
||||
|
||||
; FIXME: We should continue to report failure even after first.
|
||||
; Second Hunk
|
||||
; DIFF-NOT: -BLOCK2_PASS
|
||||
; DIFF-NOT: +BLOCK2_FAIL
|
||||
18
llvm/test/FileCheck/diff/diff-next-stray-line.txt
Normal file
18
llvm/test/FileCheck/diff/diff-next-stray-line.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
LINE1
|
||||
STRAY
|
||||
LINE2
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: LINE1
|
||||
; CHECK-NEXT: LINE2
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: LINE1
|
||||
; DIFF-NEXT: -LINE2
|
||||
; DIFF-NEXT: +STRAY
|
||||
; DIFF-NEXT: LINE2
|
||||
13
llvm/test/FileCheck/diff/diff-regex-escaping.txt
Normal file
13
llvm/test/FileCheck/diff/diff-regex-escaping.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
hello world
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: hello {{U}}niverse
|
||||
|
||||
; DIFF: @@ -[[#]] +1 @@
|
||||
; DIFF-NEXT: -hello {{[{][{]U[}][}]}}niverse
|
||||
; DIFF-NEXT: +hello world
|
||||
30
llvm/test/FileCheck/diff/diff-resync-after-noise.txt
Normal file
30
llvm/test/FileCheck/diff/diff-resync-after-noise.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
HEADER
|
||||
Noise1
|
||||
Noise2
|
||||
Noise3
|
||||
Noise4
|
||||
TARGET_BLOCK
|
||||
Stray1
|
||||
Stray2
|
||||
MATCH_ME
|
||||
NEXT_LINE
|
||||
Noise5
|
||||
Noise6
|
||||
|
||||
;--- check.txt
|
||||
; CHECK-LABEL: HEADER
|
||||
; CHECK: TARGET_BLOCK
|
||||
; CHECK: MATCH_ME
|
||||
; CHECK-NEXT: WRONG_NEXT
|
||||
; CHECK: END_OF_FUNCTION
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: MATCH_ME
|
||||
; DIFF-NEXT: -WRONG_NEXT
|
||||
; DIFF-NEXT: +NEXT_LINE
|
||||
; DIFF-NEXT: Noise5
|
||||
23
llvm/test/FileCheck/diff/diff-resync-high-noise.txt
Normal file
23
llvm/test/FileCheck/diff/diff-resync-high-noise.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
START
|
||||
Noise1
|
||||
Noise2
|
||||
Noise3
|
||||
Noise4
|
||||
TARGET
|
||||
END
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: START
|
||||
; CHECK-NEXT: TARGET
|
||||
; CHECK: END
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: START
|
||||
; DIFF-NEXT: -TARGET
|
||||
; DIFF-NEXT: +Noise1
|
||||
; DIFF-NEXT: Noise2
|
||||
17
llvm/test/FileCheck/diff/diff-skipped-expected.txt
Normal file
17
llvm/test/FileCheck/diff/diff-skipped-expected.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- input.txt
|
||||
ALPHA
|
||||
GAMMA
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: ALPHA
|
||||
; CHECK: BETA
|
||||
; CHECK: GAMMA
|
||||
|
||||
; DIFF: @@ -[[#]] +[[#]] @@
|
||||
; DIFF-NEXT: ALPHA
|
||||
; DIFF-NEXT: -BETA
|
||||
; DIFF-NEXT: +GAMMA
|
||||
15
llvm/test/FileCheck/diff/diff-stress-large-input.txt
Normal file
15
llvm/test/FileCheck/diff/diff-stress-large-input.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
; RUN: split-file %s %t
|
||||
; RUN: %python -c "for i in range(5000): print(f'DATA_{i}')" > %t/input.txt
|
||||
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
|
||||
; RUN: | FileCheck %s --check-prefix=DIFF
|
||||
|
||||
;--- check.txt
|
||||
; CHECK: DATA_0
|
||||
; CHECK: DATA_2500_WRONG
|
||||
; CHECK: DATA_4999
|
||||
|
||||
; DIFF: @@ -2 +21 @@
|
||||
; DIFF-NEXT: DATA_19
|
||||
; DIFF-NEXT: -DATA_2500_WRONG
|
||||
; DIFF-NEXT: +DATA_20
|
||||
; DIFF-NEXT: DATA_21
|
||||
@@ -113,6 +113,13 @@ static cl::opt<bool> VerboseVerbose(
|
||||
"issues, or add it to the input dump if enabled. Implies\n"
|
||||
"-v.\n"));
|
||||
|
||||
static cl::opt<DiffFormatType> DiffFormat(
|
||||
"diff", cl::desc("Show mismatches using a diff-style format.\n"),
|
||||
cl::ValueOptional,
|
||||
cl::values(clEnumValN(None, "none", "Standard FileCheck diagnostics"),
|
||||
clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
|
||||
clEnumValN(Unified, "", "")));
|
||||
|
||||
// The order of DumpInputValue members affects their precedence, as documented
|
||||
// for -dump-input below.
|
||||
enum DumpInputValue {
|
||||
@@ -787,6 +794,7 @@ int main(int argc, char **argv) {
|
||||
if (GlobalDefineError)
|
||||
return 2;
|
||||
|
||||
Req.DiffFormat = DiffFormat;
|
||||
Req.AllowEmptyInput = AllowEmptyInput;
|
||||
Req.AllowUnusedPrefixes = AllowUnusedPrefixes;
|
||||
Req.EnableVarScope = EnableVarScope;
|
||||
@@ -858,8 +866,9 @@ int main(int argc, char **argv) {
|
||||
DumpInput == DumpInputNever ? nullptr : &Diags)
|
||||
? EXIT_SUCCESS
|
||||
: 1;
|
||||
if (DumpInput == DumpInputAlways ||
|
||||
(ExitCode == 1 && DumpInput == DumpInputFail)) {
|
||||
if ((DumpInput == DumpInputAlways ||
|
||||
(ExitCode == 1 && DumpInput == DumpInputFail)) &&
|
||||
Req.DiffFormat == DiffFormatType::None) {
|
||||
errs() << "\n"
|
||||
<< "Input file: " << InputFilename << "\n"
|
||||
<< "Check file: " << CheckFilename << "\n"
|
||||
|
||||
Reference in New Issue
Block a user