diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst index 22101d3ef2ac..d99cbbdd9f36 100644 --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -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 + + 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 Dump input to stderr, adding annotations representing currently enabled diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h index b44ed8ed3f83..7896a942a346 100644 --- a/llvm/include/llvm/FileCheck/FileCheck.h +++ b/llvm/include/llvm/FileCheck/FileCheck.h @@ -27,6 +27,9 @@ class MemoryBuffer; class SourceMgr; template class SmallVectorImpl; +// Diff the output on failures. +enum DiffFormatType { None, Unified }; + /// Contains info about various FileCheck options. struct FileCheckRequest { std::vector CheckPrefixes; @@ -43,6 +46,7 @@ struct FileCheckRequest { bool AllowDeprecatedDagOverlap = false; bool Verbose = false; bool VerboseVerbose = false; + DiffFormatType DiffFormat = DiffFormatType::None; }; namespace Check { diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp index d50e5d9cb088..307be0b6e2fa 100644 --- a/llvm/lib/FileCheck/FileCheck.cpp +++ b/llvm/lib/FileCheck/FileCheck.cpp @@ -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 #include #include @@ -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 *Diags) const { + std::vector *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 *Diags) const { + std::vector *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 *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 *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 *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 *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; } diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h index 5851cfc4b5d5..58a03d280b65 100644 --- a/llvm/lib/FileCheck/FileCheckImpl.h +++ b/llvm/lib/FileCheck/FileCheckImpl.h @@ -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 *Diags) const; + std::vector *Diags, + const FileCheckRequest &Req) const; void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - std::vector *Diags) const; + std::vector *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; diff --git a/llvm/test/FileCheck/diff/diff-blank-stray.txt b/llvm/test/FileCheck/diff/diff-blank-stray.txt new file mode 100644 index 000000000000..6065c74b66f4 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-blank-stray.txt @@ -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: + diff --git a/llvm/test/FileCheck/diff/diff-empty-check.txt b/llvm/test/FileCheck/diff/diff-empty-check.txt new file mode 100644 index 000000000000..bba0b9993627 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-empty-check.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-label-boundary.txt b/llvm/test/FileCheck/diff/diff-label-boundary.txt new file mode 100644 index 000000000000..cb9595f76014 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-label-boundary.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt new file mode 100644 index 000000000000..a04f2e84ac70 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt @@ -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: } diff --git a/llvm/test/FileCheck/diff/diff-label-next.txt b/llvm/test/FileCheck/diff/diff-label-next.txt new file mode 100644 index 000000000000..240cd14596da --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-label-next.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-multi-block.txt b/llvm/test/FileCheck/diff/diff-multi-block.txt new file mode 100644 index 000000000000..2a53fb6fdd9c --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-multi-block.txt @@ -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. diff --git a/llvm/test/FileCheck/diff/diff-multi-failres.txt b/llvm/test/FileCheck/diff/diff-multi-failres.txt new file mode 100644 index 000000000000..85408544e207 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-multi-failres.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-multi-mismatch.txt b/llvm/test/FileCheck/diff/diff-multi-mismatch.txt new file mode 100644 index 000000000000..ace3d7cd093b --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-multi-mismatch.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-next-stray-line.txt b/llvm/test/FileCheck/diff/diff-next-stray-line.txt new file mode 100644 index 000000000000..dd19a0e2c6dd --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-next-stray-line.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-regex-escaping.txt b/llvm/test/FileCheck/diff/diff-regex-escaping.txt new file mode 100644 index 000000000000..8f3ebc6163f0 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-regex-escaping.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-resync-after-noise.txt b/llvm/test/FileCheck/diff/diff-resync-after-noise.txt new file mode 100644 index 000000000000..854ee188b294 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-resync-after-noise.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-resync-high-noise.txt b/llvm/test/FileCheck/diff/diff-resync-high-noise.txt new file mode 100644 index 000000000000..d7ecfe48d3b7 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-resync-high-noise.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-skipped-expected.txt b/llvm/test/FileCheck/diff/diff-skipped-expected.txt new file mode 100644 index 000000000000..f8b4d2f303bb --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-skipped-expected.txt @@ -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 diff --git a/llvm/test/FileCheck/diff/diff-stress-large-input.txt b/llvm/test/FileCheck/diff/diff-stress-large-input.txt new file mode 100644 index 000000000000..68488d4b39a1 --- /dev/null +++ b/llvm/test/FileCheck/diff/diff-stress-large-input.txt @@ -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 diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp index 12fdbfd45279..f200e94becaa 100644 --- a/llvm/utils/FileCheck/FileCheck.cpp +++ b/llvm/utils/FileCheck/FileCheck.cpp @@ -113,6 +113,13 @@ static cl::opt VerboseVerbose( "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); +static cl::opt 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"