[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:
Shivam Gupta
2026-04-18 11:47:58 +05:30
committed by GitHub
parent 4b0dd87d3a
commit 40333cde2b
19 changed files with 552 additions and 29 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;

View 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: +

View 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

View 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

View 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: }

View 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

View 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.

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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"