Index: llvm/docs/CommandGuide/FileCheck.rst =================================================================== --- llvm/docs/CommandGuide/FileCheck.rst +++ llvm/docs/CommandGuide/FileCheck.rst @@ -77,9 +77,10 @@ -verify``. With this option FileCheck will verify that input does not contain warnings not covered by any ``CHECK:`` patterns. -.. option:: --dump-input-on-failure +.. option:: --dump-input - When the check fails, dump all of the original input. + When the check fails, dump all of the original input either 'always', on + 'fail', or 'never'. .. option:: --enable-var-scope Index: llvm/include/llvm/Support/FileCheck.h =================================================================== --- llvm/include/llvm/Support/FileCheck.h +++ llvm/include/llvm/Support/FileCheck.h @@ -18,6 +18,7 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Regex.h" #include "llvm/Support/SourceMgr.h" +#include #include #include @@ -62,6 +63,8 @@ }; } +struct FileCheckDiag; + class FileCheckPattern { SMLoc PatternLoc; @@ -105,13 +108,15 @@ const StringMap &VariableTable, SMRange MatchRange = None) const; void PrintFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable) const; + const StringMap &VariableTable, + std::list *Diags) const; bool hasVariable() const { return !(VariableUses.empty() && VariableDefs.empty()); } Check::FileCheckType getCheckTy() const { return CheckTy; } + unsigned getLineNumber() const { return LineNumber; } private: bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM); @@ -123,6 +128,43 @@ size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); }; +//===----------------------------------------------------------------------===// +/// Summary of a FileCheck diagnostic. +//===----------------------------------------------------------------------===// + +struct FileCheckDiag { + /// What is the FileCheck directive for this diagnostic? + Check::FileCheckType CheckTy; + /// Where is the FileCheck directive for this diagnostic? + unsigned CheckLine, CheckCol; + /// What kind of match result does this diagnostic describe? + /// + /// There might be more than one of these for the same directive. For + /// example, there might be several discards before either a final or fail, + /// and there might be a fuzzy match after a fail. + /// + /// We iterate these types, so they must have contiguous values in + /// [0, MatchTypeCount). Moreover, keep match types together if they use the + /// same mark in annotated input dumps or else printing of the annotation key + /// will malfunction. + enum MatchType { + MatchFinalAndExpected, //< the final match for an expected pattern + MatchTypeFirst = MatchFinalAndExpected, + MatchFinalButExcluded, //< the final match for an excluded pattern + MatchFinalButIllegal, //< the final but illegal match for an expected pattern + MatchDiscard, //< a discarded match for an expected pattern + MatchNoneAndExcluded, //< no match for an excluded pattern + MatchNoneButExpected, //< no match for an expected pattern + MatchFuzzy, //< a fuzzy match (because no perfect match) + MatchTypeCount, + } MatchTy; + /// The match range if MatchTy is not MatchNoneAndExcluded or + /// MatchNoneButExpected, or the search range otherwise. + unsigned InputStartLine, InputStartCol, InputEndLine, InputEndCol; + FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy, + SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange); +}; + //===----------------------------------------------------------------------===// // Check Strings. //===----------------------------------------------------------------------===// @@ -147,18 +189,20 @@ size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, size_t &MatchLen, StringMap &VariableTable, - FileCheckRequest &Req) const; + FileCheckRequest &Req, std::list *Diags) const; bool CheckNext(const SourceMgr &SM, StringRef Buffer) const; bool CheckSame(const SourceMgr &SM, StringRef Buffer) const; bool CheckNot(const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, StringMap &VariableTable, - const FileCheckRequest &Req) const; + const FileCheckRequest &Req, + std::list *Diags) const; size_t CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, StringMap &VariableTable, - const FileCheckRequest &Req) const; + const FileCheckRequest &Req, + std::list *Diags) const; }; /// FileCheck class takes the request and exposes various methods that @@ -195,7 +239,8 @@ /// /// Returns false if the input fails to satisfy the checks. bool CheckInput(SourceMgr &SM, StringRef Buffer, - ArrayRef CheckStrings); + ArrayRef CheckStrings, + std::list *Diags = nullptr); }; } // namespace llvm #endif Index: llvm/include/llvm/Support/WithColor.h =================================================================== --- llvm/include/llvm/Support/WithColor.h +++ llvm/include/llvm/Support/WithColor.h @@ -36,10 +36,11 @@ /// color. class WithColor { raw_ostream &OS; - /// Determine whether colors should be displayed. - bool colorsEnabled(raw_ostream &OS); public: + /// Determine whether colors should be displayed. + static bool colorsEnabled(raw_ostream &OS); + /// To be used like this: WithColor(OS, HighlightColor::String) << "text"; WithColor(raw_ostream &OS, HighlightColor S); ~WithColor(); Index: llvm/lib/Support/FileCheck.cpp =================================================================== --- llvm/lib/Support/FileCheck.cpp +++ llvm/lib/Support/FileCheck.cpp @@ -410,7 +410,8 @@ void FileCheckPattern::PrintFuzzyMatch( const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable) const { + const StringMap &VariableTable, + std::list *Diags) 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 @@ -444,8 +445,13 @@ // reasonable and not equal to what we showed in the "scanning from here" // line. if (Best && Best != StringRef::npos && BestQuality < 50) { - SM.PrintMessage(SMLoc::getFromPointer(Buffer.data() + Best), - SourceMgr::DK_Note, "possible intended match here"); + SMLoc MatchStart = SMLoc::getFromPointer(Buffer.data() + Best); + SMRange MatchRange(MatchStart, MatchStart); + SM.PrintMessage(MatchStart, SourceMgr::DK_Note, + "possible intended match here"); + if (Diags) + Diags->emplace_back(SM, getCheckTy(), getLoc(), + FileCheckDiag::MatchFuzzy, MatchRange); // 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. @@ -527,6 +533,22 @@ return StringRef(OutputBuffer.data(), OutputBuffer.size() - 1); } +FileCheckDiag::FileCheckDiag(const SourceMgr &SM, + const Check::FileCheckType &CheckTy, + SMLoc CheckLoc, MatchType MatchTy, + SMRange InputRange) + : CheckTy(CheckTy), MatchTy(MatchTy) { + auto Start = SM.getLineAndColumn(InputRange.Start); + auto End = SM.getLineAndColumn(InputRange.End); + InputStartLine = Start.first; + InputStartCol = Start.second; + InputEndLine = End.first; + InputEndCol = End.second; + Start = SM.getLineAndColumn(CheckLoc); + CheckLine = Start.first; + CheckCol = Start.second; +} + static bool IsPartOfWord(char c) { return (isalnum(c) || c == '-' || c == '_'); } @@ -843,42 +865,60 @@ return false; } +static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy, + const SourceMgr &SM, SMLoc Loc, + Check::FileCheckType CheckTy, + StringRef Buffer, size_t Pos, size_t Len, + std::list *Diags) { + SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos); + SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len); + SMRange Range(Start, End); + if (Diags) + Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range); + return Range; +} + static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, StringRef Buffer, StringMap &VariableTable, size_t MatchPos, size_t MatchLen, - const FileCheckRequest &Req) { + const FileCheckRequest &Req, + std::list *Diags) { if (ExpectedMatch) { if (!Req.Verbose) return; if (!Req.VerboseVerbose && Pat.getCheckTy() == Check::CheckEOF) return; } - SMLoc MatchStart = SMLoc::getFromPointer(Buffer.data() + MatchPos); - SMLoc MatchEnd = SMLoc::getFromPointer(Buffer.data() + MatchPos + MatchLen); - SMRange MatchRange(MatchStart, MatchEnd); + SMRange MatchRange = ProcessMatchResult( + ExpectedMatch ? FileCheckDiag::MatchFinalAndExpected + : FileCheckDiag::MatchFinalButExcluded, + SM, Loc, Pat.getCheckTy(), Buffer, MatchPos, MatchLen, Diags); SM.PrintMessage( Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, CheckTypeName(Prefix, Pat.getCheckTy()) + ": " + (ExpectedMatch ? "expected" : "excluded") + " string found in input"); - SM.PrintMessage(MatchStart, SourceMgr::DK_Note, "found here", {MatchRange}); + SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", + {MatchRange}); Pat.PrintVariableUses(SM, Buffer, VariableTable, MatchRange); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, const FileCheckString &CheckStr, StringRef Buffer, StringMap &VariableTable, size_t MatchPos, - size_t MatchLen, FileCheckRequest &Req) { + size_t MatchLen, FileCheckRequest &Req, + std::list *Diags) { PrintMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - Buffer, VariableTable, MatchPos, MatchLen, Req); + Buffer, VariableTable, MatchPos, MatchLen, Req, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, - StringRef Prefix, SMLoc Loc, const FileCheckPattern &Pat, - StringRef Buffer, + StringRef Prefix, SMLoc Loc, + const FileCheckPattern &Pat, StringRef Buffer, StringMap &VariableTable, - bool VerboseVerbose) { + bool VerboseVerbose, + std::list *Diags) { if (!ExpectedMatch && !VerboseVerbose) return; @@ -892,22 +932,26 @@ // Print the "scanning from here" line. If the current position is at the // end of a line, advance to the start of the next line. Buffer = Buffer.substr(Buffer.find_first_not_of(" \t\n\r")); - - SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note, - "scanning from here"); + SMRange SearchRange = ProcessMatchResult( + ExpectedMatch ? FileCheckDiag::MatchNoneButExpected + : FileCheckDiag::MatchNoneAndExcluded, + SM, Loc, Pat.getCheckTy(), Buffer, 0, Buffer.size(), Diags); + SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here"); // Allow the pattern to print additional information if desired. Pat.PrintVariableUses(SM, Buffer, VariableTable); + if (ExpectedMatch) - Pat.PrintFuzzyMatch(SM, Buffer, VariableTable); + Pat.PrintFuzzyMatch(SM, Buffer, VariableTable, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, const FileCheckString &CheckStr, StringRef Buffer, StringMap &VariableTable, - bool VerboseVerbose) { + bool VerboseVerbose, + std::list *Diags) { PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat, - Buffer, VariableTable, VerboseVerbose); + Buffer, VariableTable, VerboseVerbose, Diags); } /// Count the number of newlines in the specified range. @@ -935,9 +979,10 @@ /// Match check string and its "not strings" and/or "dag strings". size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer, - bool IsLabelScanMode, size_t &MatchLen, - StringMap &VariableTable, - FileCheckRequest &Req) const { + bool IsLabelScanMode, size_t &MatchLen, + StringMap &VariableTable, + FileCheckRequest &Req, + std::list *Diags) const { size_t LastPos = 0; std::vector NotStrings; @@ -947,7 +992,7 @@ // over the block again (including the last CHECK-LABEL) in normal mode. if (!IsLabelScanMode) { // Match "dag strings" (with mixed "not strings" if any). - LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable, Req); + LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable, Req, Diags); if (LastPos == StringRef::npos) return StringRef::npos; } @@ -956,10 +1001,12 @@ StringRef MatchBuffer = Buffer.substr(LastPos); size_t MatchPos = Pat.Match(MatchBuffer, MatchLen, VariableTable); if (MatchPos == StringRef::npos) { - PrintNoMatch(true, SM, *this, MatchBuffer, VariableTable, Req.VerboseVerbose); + PrintNoMatch(true, SM, *this, MatchBuffer, VariableTable, + Req.VerboseVerbose, Diags); return StringRef::npos; } - PrintMatch(true, SM, *this, MatchBuffer, VariableTable, MatchPos, MatchLen, Req); + PrintMatch(true, SM, *this, MatchBuffer, VariableTable, MatchPos, MatchLen, + Req, Diags); // Similar to the above, in "label-scan mode" we can't yet handle CHECK-NEXT // or CHECK-NOT @@ -968,17 +1015,35 @@ // 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)) { + if (Diags) { + if (!Req.Verbose) + ProcessMatchResult(FileCheckDiag::MatchFinalButIllegal, SM, Loc, + Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen, + Diags); + else + Diags->rbegin()->MatchTy = FileCheckDiag::MatchFinalButIllegal; + } return StringRef::npos; + } // If this check is a "CHECK-SAME", verify that the previous match was on // the same line (i.e. that there is no newline between them). - if (CheckSame(SM, SkippedRegion)) + if (CheckSame(SM, SkippedRegion)) { + if (Diags) { + if (!Req.Verbose) + ProcessMatchResult(FileCheckDiag::MatchFinalButIllegal, SM, Loc, + Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen, + Diags); + else + Diags->rbegin()->MatchTy = FileCheckDiag::MatchFinalButIllegal; + } return StringRef::npos; + } // If this match had "not strings", verify that they don't exist in the // skipped region. - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req)) + if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req, Diags)) return StringRef::npos; } @@ -1061,10 +1126,11 @@ } /// Verify there's no "not strings" in the given buffer. -bool FileCheckString::CheckNot(const SourceMgr &SM, StringRef Buffer, - const std::vector &NotStrings, - StringMap &VariableTable, - const FileCheckRequest &Req) const { +bool FileCheckString::CheckNot( + const SourceMgr &SM, StringRef Buffer, + const std::vector &NotStrings, + StringMap &VariableTable, const FileCheckRequest &Req, + std::list *Diags) const { for (const FileCheckPattern *Pat : NotStrings) { assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); @@ -1073,12 +1139,12 @@ if (Pos == StringRef::npos) { PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, Buffer, - VariableTable, Req.VerboseVerbose); + VariableTable, Req.VerboseVerbose, Diags); continue; } PrintMatch(false, SM, Prefix, Pat->getLoc(), *Pat, Buffer, VariableTable, - Pos, MatchLen, Req); + Pos, MatchLen, Req, Diags); return true; } @@ -1087,10 +1153,12 @@ } /// Match "dag strings" and their mixed "not strings". -size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer, - std::vector &NotStrings, - StringMap &VariableTable, - const FileCheckRequest &Req) const { +size_t +FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer, + std::vector &NotStrings, + StringMap &VariableTable, + const FileCheckRequest &Req, + std::list *Diags) const { if (DagNotStrings.empty()) return 0; @@ -1134,14 +1202,14 @@ // that group of CHECK-DAGs fails immediately. if (MatchPosBuf == StringRef::npos) { PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, MatchBuffer, - VariableTable, Req.VerboseVerbose); + VariableTable, Req.VerboseVerbose, Diags); return StringRef::npos; } // Re-calc it as the offset relative to the start of the original string. MatchPos += MatchPosBuf; if (Req.VerboseVerbose) PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, Buffer, VariableTable, - MatchPos, MatchLen, Req); + MatchPos, MatchLen, Req, Diags); MatchRange M{MatchPos, MatchPos + MatchLen}; if (Req.AllowDeprecatedDagOverlap) { // We don't need to track all matches in this mode, so we just maintain @@ -1178,12 +1246,14 @@ SM.PrintMessage(OldStart, SourceMgr::DK_Note, "match discarded, overlaps earlier DAG match here", {OldRange}); + if (Diags) + Diags->rbegin()->MatchTy = FileCheckDiag::MatchDiscard; } MatchPos = MI->End; } if (!Req.VerboseVerbose) PrintMatch(true, SM, Prefix, Pat.getLoc(), Pat, Buffer, VariableTable, - MatchPos, MatchLen, Req); + MatchPos, MatchLen, Req, Diags); // Handle the end of a CHECK-DAG group. if (std::next(PatItr) == PatEnd || @@ -1194,7 +1264,7 @@ // region. StringRef SkippedRegion = Buffer.slice(StartPos, MatchRanges.begin()->Pos); - if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req)) + if (CheckNot(SM, SkippedRegion, NotStrings, VariableTable, Req, Diags)) return StringRef::npos; // Clear "not strings". NotStrings.clear(); @@ -1275,7 +1345,8 @@ /// /// Returns false if the input fails to satisfy the checks. bool llvm::FileCheck::CheckInput(SourceMgr &SM, StringRef Buffer, - ArrayRef CheckStrings) { + ArrayRef CheckStrings, + std::list *Diags) { bool ChecksFailed = false; /// VariableTable - This holds all the current filecheck variables. @@ -1298,9 +1369,8 @@ // Scan to next CHECK-LABEL match, ignoring CHECK-NOT and CHECK-DAG size_t MatchLabelLen = 0; - size_t MatchLabelPos = - CheckLabelStr.Check(SM, Buffer, true, MatchLabelLen, VariableTable, - Req); + size_t MatchLabelPos = CheckLabelStr.Check( + SM, Buffer, true, MatchLabelLen, VariableTable, Req, Diags); if (MatchLabelPos == StringRef::npos) // Immediately bail of CHECK-LABEL fails, nothing else we can do. return false; @@ -1319,8 +1389,8 @@ // Check each string within the scanned region, including a second check // of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG) size_t MatchLen = 0; - size_t MatchPos = - CheckStr.Check(SM, CheckRegion, false, MatchLen, VariableTable, Req); + size_t MatchPos = CheckStr.Check(SM, CheckRegion, false, MatchLen, + VariableTable, Req, Diags); if (MatchPos == StringRef::npos) { ChecksFailed = true; Index: llvm/test/FileCheck/dump-input-annotations.txt =================================================================== --- /dev/null +++ llvm/test/FileCheck/dump-input-annotations.txt @@ -0,0 +1,353 @@ +;-------------------------------------------------- +; Use -strict-whitespace to check marker alignment here. +; (Also check multiline marker where start/end columns vary across lines.) +; +; In the remaining checks, don't use -strict-whitespace and thus check just the +; presence, order, and lengths of markers. That way, if we ever change padding +; within line labels, we don't have to adjust so many tests. +;-------------------------------------------------- + +; RUN: echo 'hello world' > %t.in +; RUN: echo 'goodbye' >> %t.in +; RUN: echo 'world' >> %t.in + +; RUN: echo 'CHECK: hello' > %t.chk +; RUN: echo 'CHECK: universe' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -strict-whitespace -match-full-lines -check-prefix=ALIGN %s + +; ALIGN:Full input was: +; ALIGN-NEXT:<<<<<< +; ALIGN-NEXT: 1: hello world +; ALIGN-NEXT:chk:1 ^~~~~ +; ALIGN-NEXT:chk:2 X~~~~ +; ALIGN-NEXT: 2: goodbye +; ALIGN-NEXT:chk:2 ~~~~~~~ +; ALIGN-NEXT: 3: world +; ALIGN-NEXT:chk:2 ~~~~~ +; ALIGN-NEXT:>>>>>> +; ALIGN-NOT:{{.}} + +;-------------------------------------------------- +; CHECK (also: multi-line search range, fuzzy match) +;-------------------------------------------------- + +; Good match and no match. + +; RUN: echo 'hello' > %t.in +; RUN: echo 'again' >> %t.in +; RUN: echo 'whirled' >> %t.in + +; RUN: echo 'CHECK: hello' > %t.chk +; RUN: echo 'CHECK: world' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=CHK +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=CHK,CHK-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=CHK,CHK-V + +; CHK: <<<<<< +; CHK-NEXT: 1: hello +; CHK-V-NEXT: chk:1 ^~~~~ +; CHK-NEXT: 2: again +; CHK-NEXT: chk:2'0 X~~~~ +; CHK-NEXT: 3: whirled +; CHK-NEXT: chk:2'0 ~~~~~~~ +; CHK-NEXT: chk:2'1 ? +; CHK-NEXT: >>>>>> +; CHK-NOT: {{.}} + +;-------------------------------------------------- +; CHECK-NEXT (also: EOF search-range, illegal match) +;-------------------------------------------------- + +; Good match and no match. + +; RUN: echo 'hello' > %t.in +; RUN: echo 'again' >> %t.in + +; RUN: echo 'CHECK: hello' > %t.chk +; RUN: echo 'CHECK-NEXT: again' >> %t.chk +; RUN: echo 'CHECK-NEXT: world' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=NXT +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=NXT,NXT-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=NXT,NXT-V,NXT-VV + +; NXT: <<<<<< +; NXT-NEXT: 1: hello +; NXT-V-NEXT: chk:1 ^~~~~ +; NXT-NEXT: 2: again +; NXT-V-NEXT: nxt:2 ^~~~~ +; NXT-NEXT: 3: +; NXT-NEXT: nxt:3 X +; NXT-NEXT: >>>>>> +; NXT-NOT: {{.}} + +; Illegal match. + +; RUN: echo 'yonder' >> %t.in +; RUN: echo 'world' >> %t.in + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=NXT2 + +; NXT2: <<<<<< +; NXT2-NEXT: 1: hello +; NXT2-NEXT: 2: again +; NXT2-NEXT: 3: yonder +; NXT2-NEXT: 4: world +; NXT2-NEXT: nxt:3 !~~~~ +; NXT2-NEXT: >>>>>> +; NXT2-NOT: {{.}} + +;-------------------------------------------------- +; CHECK-SAME (also: multiple annotations per line, single-char search range, +; illegal match) +;-------------------------------------------------- + +; Good match and no match. + +; RUN: echo 'hello world!' > %t.in + +; RUN: echo 'CHECK: hello' > %t.chk +; RUN: echo 'CHECK-SAME: world' >> %t.chk +; RUN: echo 'CHECK-SAME: again' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=SAM +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=SAM,SAM-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=SAM,SAM-V,SAM-VV + +; SAM: <<<<<< +; SAM-NEXT: 1: hello world! +; SAM-V-NEXT: chk:1 ^~~~~ +; SAM-V-NEXT: sam:2 ^~~~~ +; SAM-NEXT: sam:3 X +; SAM-NEXT: >>>>>> +; SAM-NOT: {{.}} + +; Illegal match. + +; RUN: echo 'again' >> %t.in + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=SAM2 + +; SAM2: <<<<<< +; SAM2-NEXT: 1: hello world! +; SAM2-NEXT: chk:1 ^~~~~ +; SAM2-NEXT: sam:2 ^~~~~ +; SAM2-NEXT: 2: again +; SAM2-NEXT: sam:3 !~~~~ +; SAM2-NEXT: >>>>>> +; SAM2-NOT: {{.}} + +;-------------------------------------------------- +; CHECK-EMPTY (also: search range ends at label, single-char match, illegal +; match) +;-------------------------------------------------- + +; Good match and no match. +; +; CHECK-EMPTY always seems to match an empty line at EOF (illegally when it's +; not the next line) unless either (1) the last line is non-empty and has no +; newline or (2) there's a CHECK-LABEL to end the search range before EOF. We +; choose scenario 2 to check the case of no match. + +; RUN: echo 'hello' > %t.in +; RUN: echo '' >> %t.in +; RUN: echo 'world' >> %t.in +; RUN: echo 'label' >> %t.in + +; RUN: echo 'CHECK: hello' > %t.chk +; RUN: echo 'CHECK-EMPTY:' >> %t.chk +; RUN: echo 'CHECK-EMPTY:' >> %t.chk +; RUN: echo 'CHECK-LABEL: label' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=EMP +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=EMP,EMP-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=EMP,EMP-V,EMP-VV + +; EMP: <<<<<< +; EMP-NEXT: 1: hello +; EMP-V-NEXT: chk:1 ^~~~~ +; EMP-NEXT: 2: +; EMP-V-NEXT: emp:2 ^ +; EMP-NEXT: 3: world +; EMP-NEXT: emp:3 X~~~~ +; EMP-NEXT: 4: label +; EMP-NEXT: emp:3 ~~~~~ +; EMP-V-NEXT: lab:4 ^~~~~ +; EMP-NEXT: >>>>>> +; EMP-NOT: {{.}} + +; Illegal match. + +; RUN: echo 'hello' > %t.in +; RUN: echo 'world' >> %t.in + +; RUN: echo 'CHECK: hello' > %t.chk +; RUN: echo 'CHECK-EMPTY:' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=EMP2 +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=EMP2,EMP2-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=EMP2,EMP2-V,EMP2-VV + +; EMP2: <<<<<< +; EMP2-NEXT: 1: hello +; EMP2-V-NEXT: chk:1 ^~~~~ +; EMP2-NEXT: 2: world +; EMP2-NEXT: 3: +; EMP2-NEXT: emp:2 ! +; EMP2-NEXT: >>>>>> +; EMP2-NOT: {{.}} + +;-------------------------------------------------- +; CHECK-NOT (also: EOF pattern, and multiline range that ends before EOL) +;-------------------------------------------------- + +; No match (success) and illegal match. + +; RUN: echo 'hello' > %t.in +; RUN: echo 'world' >> %t.in +; RUN: echo 'again' >> %t.in + +; RUN: echo 'CHECK-NOT: goodbye' > %t.chk +; RUN: echo 'CHECK-NOT: world' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=NOT +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=NOT,NOT-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=NOT,NOT-V,NOT-VV + +; NOT: <<<<<< +; NOT-NEXT: 1: hello +; NOT-VV-NEXT: not:1 X~~~~ +; NOT-NEXT: 2: world +; NOT-VV-NEXT: not:1 ~~~~~ +; NOT-NEXT: not:2 !~~~~ +; NOT-NEXT: 3: again +; NOT-VV-NEXT: not:1 ~~~~~ +; NOT-VV-NEXT: 4: +; NOT-VV-NEXT: eof:2 ^ +; NOT-NEXT: >>>>>> +; NOT-NOT: {{.}} + +; Again, but with a CHECK instead of EOF as search range end. + +; RUN: echo 'CHECK: ain' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=NOT2 +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=NOT2,NOT2-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=NOT2,NOT2-V,NOT2-VV + +; NOT2: <<<<<< +; NOT2-NEXT: 1: hello +; NOT2-VV-NEXT: not:1 X~~~~ +; NOT2-NEXT: 2: world +; NOT2-VV-NEXT: not:1 ~~~~~ +; NOT2-NEXT: not:2 !~~~~ +; NOT2-NEXT: 3: again +; NOT2-VV-NEXT: not:1 ~~ +; NOT2-V-NEXT: chk:3 ^~~ +; NOT2-NEXT: >>>>>> +; NOT2-NOT: {{.}} + +;-------------------------------------------------- +; CHECK-DAG (also: matches in different order than directives, discarded match) +;-------------------------------------------------- + +; Good match, discarded match plus good match, and no match. + +; RUN: echo 'abc' > %t.in +; RUN: echo 'def' >> %t.in +; RUN: echo 'abc' >> %t.in + +; RUN: echo 'CHECK-DAG: def' > %t.chk +; RUN: echo 'CHECK-DAG: abc' >> %t.chk +; RUN: echo 'CHECK-DAG: abc' >> %t.chk +; RUN: echo 'CHECK-DAG: def' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG,DAG-Q +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG,DAG-V,DAG-VQ +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG,DAG-V,DAG-VV + +; DAG: <<<<<< +; DAG-NEXT: 1: abc +; DAG-V-NEXT: dag:2 ^~~ +; DAG-VV-NEXT: dag:3'0 !~~ +; DAG-NEXT: 2: def +; DAG-V-NEXT: dag:1 ^~~ +; DAG-VV-NEXT: dag:4'0 !~~ +; DAG-NEXT: 3: abc +; DAG-VQ-NEXT: dag:3 ^~~ +; DAG-VV-NEXT: dag:3'1 ^~~ +; DAG-Q-NEXT: dag:4 X~~ +; DAG-VQ-NEXT: dag:4 X~~ +; DAG-VV-NEXT: dag:4'1 X~~ +; DAG-NEXT: >>>>>> +; DAG-NOT: {{.}} + +;-------------------------------------------------- +; CHECK-LABEL +; +; FIXME: Labels sometimes produce redundant diagnostics for good matches. +; That bug is independent of but affects -dump-input. +;-------------------------------------------------- + +; Good match and no match. + +; RUN: echo 'lab0' > %t.in +; RUN: echo 'foo' >> %t.in +; RUN: echo 'lab1' >> %t.in +; RUN: echo 'bar' >> %t.in + +; RUN: echo 'CHECK-LABEL: lab0' > %t.chk +; RUN: echo 'CHECK: foo' >> %t.chk +; RUN: echo 'CHECK-LABEL: lab2' >> %t.chk + +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=LAB +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=LAB,LAB-V +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=LAB,LAB-V,LAB-VV + +; LAB: <<<<<< +; LAB-NEXT: 1: lab0 +; LAB-V-NEXT: lab:1'0 ^~~~ +; LAB-V-NEXT: lab:1'1 ^~~~ +; LAB-NEXT: 2: foo +; LAB-NEXT: lab:3'0 X~~ +; LAB-NEXT: 3: lab1 +; LAB-NEXT: lab:3'0 ~~~~ +; LAB-NEXT: lab:3'1 ? +; LAB-NEXT: 4: bar +; LAB-NEXT: lab:3'0 ~~~ +; LAB-NEXT: >>>>>> +; LAB-NOT: {{.}} + + Index: llvm/test/FileCheck/dump-input-enable.txt =================================================================== --- /dev/null +++ llvm/test/FileCheck/dump-input-enable.txt @@ -0,0 +1,118 @@ +; RUN: echo ciao > %t.good +; RUN: echo world >> %t.good + +; RUN: echo hello > %t.err +; RUN: echo world >> %t.err +; +; RUN: echo 'CHECK: ciao' > %t.check +; RUN: echo 'CHECK-NEXT: world' >> %t.check + +;-------------------------------------------------- +; unknown value +;-------------------------------------------------- + +; RUN: not FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=foobar 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=BADVAL + +; RUN: FILECHECK_DUMP_INPUT=foobar \ +; RUN: not FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=BADVAL + +BADVAL: Unrecognized value for dump-input option: foobar + +;-------------------------------------------------- +; never +;-------------------------------------------------- +; +; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=never 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP -allow-empty +; +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=never 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP +; +; RUN: FILECHECK_DUMP_INPUT=never \ +; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP -allow-empty +; +; RUN: FILECHECK_DUMP_INPUT=never \ +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP + +;-------------------------------------------------- +; fail +;-------------------------------------------------- +; +; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=fail 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP -allow-empty +; +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=fail 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-ERR +; +; RUN: FILECHECK_DUMP_INPUT=fail \ +; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP -allow-empty +; +; RUN: FILECHECK_DUMP_INPUT=fail \ +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-ERR + +;-------------------------------------------------- +; always +;-------------------------------------------------- +; +; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=always -v 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-GOOD +; +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=always 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-ERR +; +; RUN: FILECHECK_DUMP_INPUT=always \ +; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=always -v 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-GOOD +; +; RUN: FILECHECK_DUMP_INPUT=always \ +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=always 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-ERR + +;-------------------------------------------------- +; env and command line conflict +;-------------------------------------------------- +; +; RUN: FILECHECK_DUMP_INPUT=always \ +; RUN: not FileCheck -input-file %t.err %t.check -check-prefix=CHECK \ +; RUN: -match-full-lines -dump-input=never 2>&1 \ +; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-NODUMP + +; END. + +; CHECK-GOOD: Full input was: +; CHECK-GOOD-NEXT: <<<<<< +; CHECK-GOOD-NEXT: 1: ciao +; CHECK-GOOD-NEXT: chk:1 ^~~~ +; CHECK-GOOD-NEXT: 2: world +; CHECK-GOOD-NEXT: nxt:2 ^~~~~ +; CHECK-GOOD-NEXT: >>>>>> + +; CHECK-ERR: Full input was: +; CHECK-ERR-NEXT: <<<<<< +; CHECK-ERR-NEXT: 1: hello +; CHECK-ERR-NEXT: chk:1 X~~~~ +; CHECK-ERR-NEXT: 2: world +; CHECK-ERR-NEXT: chk:1 ~~~~~ +; CHECK-ERR-NEXT: >>>>>> + +; CHECK-NODUMP-NOT: <<<<<< Index: llvm/test/FileCheck/match-full-lines.txt =================================================================== --- llvm/test/FileCheck/match-full-lines.txt +++ llvm/test/FileCheck/match-full-lines.txt @@ -1,6 +1,9 @@ -// RUN: not FileCheck -match-full-lines -input-file %s %s 2>&1 \ +// In case the environment has FILECHECK_DUMP_INPUT, use -dump-input=never here +// to avoid additional output that might not match expected output. +// +// RUN: not FileCheck -dump-input=never -match-full-lines -input-file %s %s 2>&1 \ // RUN: | FileCheck --check-prefix=ERROR --implicit-check-not=error: %s -// RUN: not FileCheck -match-full-lines -strict-whitespace -input-file %s %s 2>&1 \ +// RUN: not FileCheck -dump-input=never -match-full-lines -strict-whitespace -input-file %s %s 2>&1 \ // RUN: | FileCheck --check-prefix=ERROR-STRICT --check-prefix=ERROR --implicit-check-not=error: %s Label 1 Index: llvm/test/FileCheck/verbose_mode.txt =================================================================== --- llvm/test/FileCheck/verbose_mode.txt +++ /dev/null @@ -1,17 +0,0 @@ -; RUN: not FileCheck -input-file %s %s --check-prefix=CHECK1 --match-full-lines --dump-input-on-failure 2>&1 | FileCheck %s --check-prefix=CHECKERROR --match-full-lines -; RUN: env FILECHECK_DUMP_INPUT_ON_FAILURE=1 not FileCheck -input-file %s %s --check-prefix=CHECK1 --match-full-lines 2>&1 | FileCheck %s --check-prefix=CHECKERROR --match-full-lines -; RUN: env FILECHECK_DUMP_INPUT_ON_FAILURE=1 not FileCheck -input-file %s %s --check-prefix=CHECK1 --match-full-lines --dump-input-on-failure=0 2>&1 | FileCheck %s --check-prefix=CHECKERRORNOVERBOSE --match-full-lines - -hello -world - -; CHECK1: ciao -; CHECK1-NEXT: world - -; CHECKERROR: Full input was: -; CHECKERROR-NEXT: <<<<<< -; CHECKERROR: hello -; CHECKERROR: world -; CHECKERROR: >>>>>> - -; CHECKERRORNOVERBOSE-NOT: <<<<<< Index: llvm/utils/FileCheck/FileCheck.cpp =================================================================== --- llvm/utils/FileCheck/FileCheck.cpp +++ llvm/utils/FileCheck/FileCheck.cpp @@ -18,6 +18,7 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" +#include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/FileCheck.h" using namespace llvm; @@ -84,13 +85,14 @@ "vv", cl::init(false), cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues. Implies -v.\n")); -static const char * DumpInputEnv = "FILECHECK_DUMP_INPUT_ON_FAILURE"; -static cl::opt DumpInputOnFailure( - "dump-input-on-failure", cl::init(std::getenv(DumpInputEnv)), - cl::desc("Dump original input to stderr before failing.\n" - "The value can be also controlled using\n" - "FILECHECK_DUMP_INPUT_ON_FAILURE environment variable.\n")); +static const char *DumpInputEnvValue = std::getenv("FILECHECK_DUMP_INPUT"); +static cl::opt DumpInput( + "dump-input", cl::init(DumpInputEnvValue ? DumpInputEnvValue : "never"), + cl::desc("Dump original input to stderr either 'always', on 'fail', or\n" + "'never'. The value can be also controlled using\n" + "FILECHECK_DUMP_INPUT environment variable.\n"), + cl::value_desc("mode")); typedef cl::list::const_iterator prefix_iterator; @@ -107,6 +109,470 @@ errs() << "\n"; } +struct MatchTypeStyle { + char Mark; + bool HasTildes; + raw_ostream::Colors Color; + unsigned RequiredVerbosity; // 0 = none, 1 = -v, 2 = -vv + const char *What; + MatchTypeStyle(char Mark, bool HasTildes, raw_ostream::Colors Color, + unsigned RequiredVerbosity, const char *What) + : Mark(Mark), HasTildes(HasTildes), Color(Color), + RequiredVerbosity(RequiredVerbosity), What(What) {} +}; + +static MatchTypeStyle GetMatchTypeStyle(unsigned MatchTy) { + switch (MatchTy) { + case FileCheckDiag::MatchFinalAndExpected: + return MatchTypeStyle('^', true, raw_ostream::GREEN, 1, + "the final match for an expected pattern"); + case FileCheckDiag::MatchFinalButExcluded: + return MatchTypeStyle('!', true, raw_ostream::RED, 0, + "the final match for an excluded pattern"); + case FileCheckDiag::MatchFinalButIllegal: + return MatchTypeStyle('!', true, raw_ostream::RED, 0, + "the final but illegal match for an expected " + "pattern"); + case FileCheckDiag::MatchDiscard: + return MatchTypeStyle('!', true, raw_ostream::CYAN, 2, + "a discarded match for an expected pattern"); + case FileCheckDiag::MatchNoneAndExcluded: + return MatchTypeStyle('X', true, raw_ostream::GREEN, 2, + "the search range for an unmatched excluded " + "pattern"); + case FileCheckDiag::MatchNoneButExpected: + return MatchTypeStyle('X', true, raw_ostream::RED, 0, + "the search range for an unmatched expected " + "pattern"); + case FileCheckDiag::MatchFuzzy: + return MatchTypeStyle('?', false, raw_ostream::MAGENTA, 0, + "a fuzzy match start for an otherwise unmatched " + "pattern"); + case FileCheckDiag::MatchTypeCount: + llvm_unreachable_internal("unexpected match type"); + } + llvm_unreachable_internal("unexpected match type"); +} + +static void DumpInputAnnotationKey(const FileCheckRequest &Req) { + errs() << "\nKey for input dump annotations:\n\n"; + + // Labels for input lines. + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "L"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << ": labels input line number L\n"; + + // Labels for annotation lines. + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "T:L"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << " labels the only match result for a pattern of " + << "type T from line L\n"; + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "T:L'N"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << " labels the Nth match result for a pattern of " + << "type T from line L\n"; + + // Markers on annotation lines. + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "^~~"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << " marks good match (requires -v)\n"; + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "!~~"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << " marks bad match\n"; + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "X~~"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << " marks search range when no match\n"; + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << "?"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << " marks fuzzy match when no match\n"; + + // Colors. + if (WithColor::colorsEnabled(errs())) { + errs() << " - color "; + if (Req.Verbose) { + errs().changeColor(raw_ostream::GREEN, true); + errs() << "success"; + errs().changeColor(raw_ostream::BLACK, false); + errs() << ", "; + } + errs().changeColor(raw_ostream::RED, true); + errs() << "error"; + errs().changeColor(raw_ostream::BLACK, false); + if (Req.Verbose) { + errs() << ", "; + errs().changeColor(raw_ostream::CYAN, true, true); + errs() << "unmatched"; + errs().changeColor(raw_ostream::BLACK, false, false); + if (Req.VerboseVerbose) { + errs() << ", "; + errs().changeColor(raw_ostream::CYAN, true); + errs() << "discarded"; + errs().changeColor(raw_ostream::BLACK, false); + } + } + errs() << ", "; + errs().changeColor(raw_ostream::MAGENTA, true); + errs() << "fuzzy"; + errs().changeColor(raw_ostream::BLACK, false); + errs() << '\n'; + } + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + + errs() << "\nDetailed description of currently enabled markers:\n\n"; + + unsigned Verbosity = Req.VerboseVerbose ? 2 : Req.Verbose ? 1 : 0; + for (unsigned StyleIdx = FileCheckDiag::MatchTypeFirst; + StyleIdx < FileCheckDiag::MatchTypeCount; ++StyleIdx) { + MatchTypeStyle Style = GetMatchTypeStyle(StyleIdx); + if (Verbosity < Style.RequiredVerbosity) + continue; + if (StyleIdx == FileCheckDiag::MatchTypeFirst || + Verbosity < GetMatchTypeStyle(StyleIdx - 1).RequiredVerbosity || + Style.Mark != GetMatchTypeStyle(StyleIdx - 1).Mark || + Style.HasTildes != GetMatchTypeStyle(StyleIdx - 1).HasTildes || + (WithColor::colorsEnabled(errs()) && + Style.Color != GetMatchTypeStyle(StyleIdx - 1).Color)) { + errs() << " - "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(Style.Color, true, false); + errs() << Style.Mark; + if (Style.HasTildes) + errs() << "~~"; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false, false); + if (!Style.HasTildes) + errs() << " "; + errs() << " marks "; + if (StyleIdx + 1 != FileCheckDiag::MatchTypeCount && + Verbosity >= GetMatchTypeStyle(StyleIdx + 1).RequiredVerbosity && + Style.Mark == GetMatchTypeStyle(StyleIdx + 1).Mark && + Style.HasTildes == GetMatchTypeStyle(StyleIdx + 1).HasTildes && + (!WithColor::colorsEnabled(errs()) || + Style.Color == GetMatchTypeStyle(StyleIdx + 1).Color)) + errs() << "either:\n" + << " - "; + } else + errs() << " - "; + errs() << Style.What << "\n"; + } + + if (WithColor::colorsEnabled(errs()) && Req.Verbose) { + errs() << " - "; + errs().changeColor(raw_ostream::CYAN, true, true); + errs() << "input"; + errs().changeColor(raw_ostream::BLACK, false, false); + errs() << " is style of input text with no final match for any " + << "expected pattern\n"; + } + + if (WithColor::colorsEnabled(errs())) + errs().resetColor(); +} + +/// An annotation for a single input line. +struct InputAnnotation { + /// The check file line (one-origin indexing) where the directive that + /// produced this annotation is located. + unsigned CheckLine; + /// The index of the match result for this check. + unsigned CheckDiagIndex; + /// The label for this annotation. + std::string Label; + /// What input line (one-origin indexing) this annotation marks. This might + /// be different from the starting line of the original diagnostic if this is + /// a non-initial fragment of a diagnostic that has been broken across + /// multiple lines. + unsigned InputLine; + /// The column range (one-origin indexing, open end) in which to to mark the + /// input line. If InputEndCol is UINT_MAX, treat it as the last column + /// before the newline. + unsigned InputStartCol, InputEndCol; + /// The starting char (before tildes) for marking the line. + char Mark; + /// Whether this annotation represents a final match for an expected pattern. + bool FinalAndExpectedMatch; + /// What color to use for this annotation. + raw_ostream::Colors Color; +}; + +/// Get a three-letter abbreviation for the check type. +std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { + switch (Ty) { + case Check::CheckPlain: + return "chk"; + case Check::CheckNext: + return "nxt"; + case Check::CheckSame: + return "sam"; + case Check::CheckNot: + return "not"; + case Check::CheckDAG: + return "dag"; + case Check::CheckLabel: + return "lab"; + case Check::CheckEmpty: + return "emp"; + case Check::CheckEOF: + return "eof"; + case Check::CheckBadNot: + return "bad"; + case Check::CheckNone: + llvm_unreachable("invalid FileCheckType"); + } + llvm_unreachable("unknown FileCheckType"); +} + +static void BuildInputAnnotations(const std::list &DiagList, + std::list &AnnotationList, + unsigned &LabelWidth) { + // How many diagnostics has the current check seen so far? + unsigned CheckDiagCount = 0; + // What's the widest label? + LabelWidth = 0; + for (auto DiagItr = DiagList.begin(), DiagEnd = DiagList.end(); + DiagItr != DiagEnd; ++DiagItr) { + AnnotationList.emplace_back(); + InputAnnotation &A = AnnotationList.back(); + + // Build label, which uniquely identifies this check result. + A.CheckLine = DiagItr->CheckLine; + llvm::raw_string_ostream Label(A.Label); + Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":" + << DiagItr->CheckLine; + A.CheckDiagIndex = UINT_MAX; + auto DiagNext = std::next(DiagItr); + if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && + DiagItr->CheckLine == DiagNext->CheckLine) + A.CheckDiagIndex = CheckDiagCount++; + else if (CheckDiagCount) { + A.CheckDiagIndex = CheckDiagCount; + CheckDiagCount = 0; + } + if (A.CheckDiagIndex != UINT_MAX) + Label << "'" << A.CheckDiagIndex; + else + A.CheckDiagIndex = 0; + Label.flush(); + LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); + + MatchTypeStyle MatchTyStyle = GetMatchTypeStyle(DiagItr->MatchTy); + A.Mark = MatchTyStyle.Mark; + A.Color = MatchTyStyle.Color; + A.FinalAndExpectedMatch = + DiagItr->MatchTy == FileCheckDiag::MatchFinalAndExpected; + + // Compute the mark location, and break annotation into multiple + // annotations if it spans multiple lines. + A.InputLine = DiagItr->InputStartLine; + A.InputStartCol = DiagItr->InputStartCol; + if (DiagItr->InputStartLine == DiagItr->InputEndLine) { + // Sometimes ranges are empty in order to indicate a specific point, but + // that would mean nothing would be marked, so adjust the range to + // include the following character. + A.InputEndCol = + std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); + assert((MatchTyStyle.HasTildes || + A.InputStartCol + 1 == A.InputEndCol) && + "expected input range to have only one character for marker " + "style without tildes"); + } else { + assert(MatchTyStyle.HasTildes && + "expected input range to have only one character for marker " + "style without tildes"); + assert(DiagItr->InputStartLine < DiagItr->InputEndLine && + "expected input range not to be inverted"); + A.InputEndCol = UINT_MAX; + for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; + L <= E; ++L) { + // If a range ends before the first column on a line, then it has no + // characters on that line, so there's nothing to render. + if (DiagItr->InputEndCol == 1 && L == E) + break; + AnnotationList.emplace_back(); + InputAnnotation &B = AnnotationList.back(); + B.CheckLine = A.CheckLine; + B.CheckDiagIndex = A.CheckDiagIndex; + B.Label = A.Label; + B.InputLine = L; + B.Mark = '~'; + B.InputStartCol = 1; + if (L != E) + B.InputEndCol = UINT_MAX; + else + B.InputEndCol = DiagItr->InputEndCol; + B.FinalAndExpectedMatch = A.FinalAndExpectedMatch; + B.Color = A.Color; + } + } + } +} + +static void +DumpAnnotatedInput(const FileCheckRequest &Req, StringRef InputFileText, + const std::list &AnnotationList, + unsigned LabelWidth) { + errs() << "Full input was:\n<<<<<<\n"; + + // Sort annotations. + // + // First, sort in the order of input lines to make it easier to find relevant + // annotations while iterating input lines in the implementation below. + // FileCheck diagnostics are not always reported and recorded in the order of + // input lines due to, for example, CHECK-DAG and CHECK-NOT. + // + // Second, for annotations for the same input line, sort in the order of the + // FileCheck directive's line in the check file (where there's at most one + // directive per line) and then by the index of the match result for that + // directive. The rationale of this choice is that, for any input line, this + // sort establishes a total order of annotations that, with respect to match + // results, is consistent across multiple lines, thus making match results + // easier to track from one line to the next when they span multiple lines. + std::vector Annotations(AnnotationList.begin(), + AnnotationList.end()); + std::sort(Annotations.begin(), Annotations.end(), + [](const InputAnnotation &A, const InputAnnotation &B) { + if (A.InputLine != B.InputLine) + return A.InputLine < B.InputLine; + if (A.CheckLine != B.CheckLine) + return A.CheckLine < B.CheckLine; + // FIXME: Sometimes CHECK-LABEL reports its match twice with + // other diagnostics in between, and then diag index incrementing + // fails to work properly, and then this assert fails. We should + // suppress one of those diagnostics or do a better job of + // computing this index. For now, we just produce a redundant + // CHECK-LABEL annotation. + // assert(A.CheckDiagIndex != B.CheckDiagIndex && + // "expected diagnostic indices to be unique within a " + // " check line"); + return A.CheckDiagIndex < B.CheckDiagIndex; + }); + + // Compute the width of the label column. + const unsigned char *InputFilePtr = InputFileText.bytes_begin(), + *InputFileEnd = InputFileText.bytes_end(); + unsigned LineCount = InputFileText.count('\n'); + if (InputFileEnd[-1] != '\n') + ++LineCount; + unsigned LineNoWidth = log10(LineCount) + 1; + // +3 below adds spaces (1) to the left of the (right-aligned) line numbers + // on input lines and (2) to the right of the (left-aligned) labels on + // annotation lines so that input lines and annotation lines are more + // visually distinct. For example, the spaces on the annotation lines ensure + // that input line numbers and check directive line numbers never align + // horizontally. Those line numbers might not even be for the same file. + // One space would be enough to achieve that, but more makes it even easier + // to see. + LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; + + // Print annotated input lines. + auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); + for (unsigned Line = 1; + InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; + ++Line) { + const unsigned char *InputFileLine = InputFilePtr; + + // Print right-aligned line number. + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, true); + errs() << format_decimal(Line, LabelWidth) << ": "; + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + + // For case where -v and colors are enabled, find the annotations for final + // matches for expected patterns in order to highlight everything else in + // the line. There are no such annotations if -v is disabled. + std::list FinalAndExpectedMatches; + if (Req.Verbose && WithColor::colorsEnabled(errs())) { + for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; + ++I) { + if (I->FinalAndExpectedMatch) + FinalAndExpectedMatches.push_back(*I); + } + } + + // Print numbered line with highlighting where there are no matches for + // expected patterns. + bool Newline = false; + if (Req.Verbose && WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::CYAN, true, true); + for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { + bool StartsMatch = false; + bool EndsMatch = false; + for (auto M : FinalAndExpectedMatches) { + if (Col == M.InputEndCol) + EndsMatch = true; + else if (Col == M.InputStartCol) + StartsMatch = true; + } + if (StartsMatch) + errs().changeColor(raw_ostream::BLACK, false); + else if (EndsMatch) + errs().changeColor(raw_ostream::CYAN, true, true); + if (*InputFilePtr == '\n') + Newline = true; + else + errs() << *InputFilePtr; + ++InputFilePtr; + } + if (WithColor::colorsEnabled(errs())) + errs().changeColor(raw_ostream::BLACK, false); + errs() << '\n'; + unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline; + + // Print any annotations. + while (AnnotationItr != AnnotationEnd && + AnnotationItr->InputLine == Line) { + if (WithColor::colorsEnabled(errs())) + errs().changeColor(AnnotationItr->Color, true); + // The two spaces below are where the ": " appears on input lines. + errs() << left_justify(AnnotationItr->Label, LabelWidth) << " "; + unsigned Col; + for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) + errs() << ' '; + errs() << AnnotationItr->Mark; + // If InputEndCol=UINT_MAX, stop at InputLineWidth. + for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; + ++Col) + errs() << '~'; + errs() << '\n'; + ++AnnotationItr; + } + } + + if (WithColor::colorsEnabled(errs())) + errs().resetColor(); + errs() << ">>>>>>\n"; +} + int main(int argc, char **argv) { InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv); @@ -151,6 +617,10 @@ return 2; } + if (DumpInput != "never" && DumpInput != "fail" && DumpInput != "always") { + errs() << "Unrecognized value for dump-input option: " << DumpInput << '\n'; + return 2; + } SourceMgr SM; @@ -198,10 +668,20 @@ InputFileText, InputFile.getBufferIdentifier()), SMLoc()); - int ExitCode = - FC.CheckInput(SM, InputFileText, CheckStrings) ? EXIT_SUCCESS : 1; - if (ExitCode == 1 && DumpInputOnFailure) - errs() << "Full input was:\n<<<<<<\n" << InputFileText << "\n>>>>>>\n"; + std::list DiagList; + int ExitCode = FC.CheckInput(SM, InputFileText, CheckStrings, &DiagList) + ? EXIT_SUCCESS + : 1; + + if (DumpInput == "always" || (ExitCode == 1 && DumpInput == "fail")) { + errs() << '\n'; + DumpInputAnnotationKey(Req); + std::list AnnotationList; + unsigned LabelWidth; + BuildInputAnnotations(DiagList, AnnotationList, LabelWidth); + errs() << '\n'; + DumpAnnotatedInput(Req, InputFileText, AnnotationList, LabelWidth); + } return ExitCode; } Index: llvm/utils/lit/lit/TestingConfig.py =================================================================== --- llvm/utils/lit/lit/TestingConfig.py +++ llvm/utils/lit/lit/TestingConfig.py @@ -26,7 +26,7 @@ 'LSAN_OPTIONS', 'ADB', 'ANDROID_SERIAL', 'SANITIZER_IGNORE_CVE_2016_2143', 'TMPDIR', 'TMP', 'TEMP', 'TEMPDIR', 'AVRLIT_BOARD', 'AVRLIT_PORT', - 'FILECHECK_DUMP_INPUT_ON_FAILURE'] + 'FILECHECK_DUMP_INPUT'] for var in pass_vars: val = os.environ.get(var, '') # Check for empty string as some variables such as LD_PRELOAD cannot be empty