[FileCheck] Annotate input dump (5/7)

This patch implements input annotations for diagnostics enabled by -v,
which report good matches for directives.  These annotations mark
match ranges using `^~~`.

For example:

```
$ FileCheck -dump-input=help
The following description was requested by -dump-input=help to
explain the input annotations printed by -dump-input=always and
-dump-input=fail:

  - L:     labels line number L of the input file
  - T:L    labels the only match result for a pattern of type T from line L of
           the check file
  - T:L'N  labels the Nth match result for a pattern of type T from line L of
           the check file
  - ^~~    marks good match (reported if -v)
  - !~~    marks bad match, such as:
           - CHECK-NEXT on same line as previous match (error)
           - CHECK-NOT found (error)
  - X~~    marks search range when no match is found, such as:
           - CHECK-NEXT not found (error)
  - ?      marks fuzzy match when no match is found
  - colors success, error, fuzzy match, unmatched input

If you are not seeing color above or in input dumps, try: -color

$ FileCheck -v -dump-input=always check3 < input3 |& sed -n '/^<<<</,$p'
<<<<<<
         1: abc foobar def
check:1     ^~~
not:2           !~~~~~     error: no match expected
check:3                ^~~
>>>>>>

$ cat check3
CHECK:     abc
CHECK-NOT: foobar
CHECK:     def

$ cat input3
abc foobar def
```

-vv enables these annotations for FileCheck's implicit EOF patterns as
well.  For an example where EOF patterns become relevant, see patch 7
in this series.

If colors are enabled, `^~~` is green to suggest success.

-v plus color enables highlighting of input text that has no final
match for any expected pattern.  The highlight uses a cyan background
to suggest a cold section.  This highlighting can make it easier to
spot text that was intended to be matched but that failed to be
matched in a long series of good matches.

CHECK-COUNT-<num> good matches are another case where there can be
multiple match results for the same directive.

Reviewed By: george.karpenkov, probinson

Differential Revision: https://reviews.llvm.org/D53897

llvm-svn: 349422
This commit is contained in:
Joel E. Denny 2018-12-18 00:03:03 +00:00
parent 3ef7d597d8
commit edc00dc9b3
5 changed files with 138 additions and 46 deletions

View File

@ -163,6 +163,8 @@ struct FileCheckDiag {
/// example, there might be a fuzzy match after a fail.
enum MatchType {
// TODO: More members will appear with later patches in this series.
/// Indicates the final match for an expected pattern.
MatchFinalAndExpected,
/// Indicates the final match for an excluded pattern.
MatchFinalButExcluded,
/// Indicates the final match for an expected pattern, but the match is on

View File

@ -416,14 +416,19 @@ static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy,
const SourceMgr &SM, SMLoc Loc,
Check::FileCheckType CheckTy,
StringRef Buffer, size_t Pos, size_t Len,
std::vector<FileCheckDiag> *Diags) {
std::vector<FileCheckDiag> *Diags,
bool AdjustPrevDiag = false) {
SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos);
SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len);
SMRange Range(Start, End);
// TODO: The second condition will disappear when we extend this to handle
// more match types.
if (Diags && MatchTy != FileCheckDiag::MatchTypeCount)
Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range);
if (Diags && MatchTy != FileCheckDiag::MatchTypeCount) {
if (AdjustPrevDiag)
Diags->rbegin()->MatchTy = MatchTy;
else
Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range);
}
return Range;
}
@ -905,7 +910,7 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM,
return;
}
SMRange MatchRange = ProcessMatchResult(
ExpectedMatch ? FileCheckDiag::MatchTypeCount
ExpectedMatch ? FileCheckDiag::MatchFinalAndExpected
: FileCheckDiag::MatchFinalButExcluded,
SM, Loc, Pat.getCheckTy(), Buffer, MatchPos, MatchLen, Diags);
std::string Message = formatv("{0}: {1} string found in input",
@ -1060,7 +1065,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
if (CheckNext(SM, SkippedRegion)) {
ProcessMatchResult(FileCheckDiag::MatchFinalButWrongLine, SM, Loc,
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
Diags);
Diags, Req.Verbose);
return StringRef::npos;
}
@ -1069,7 +1074,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
if (CheckSame(SM, SkippedRegion)) {
ProcessMatchResult(FileCheckDiag::MatchFinalButWrongLine, SM, Loc,
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
Diags);
Diags, Req.Verbose);
return StringRef::npos;
}
@ -1278,6 +1283,8 @@ FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
SM.PrintMessage(OldStart, SourceMgr::DK_Note,
"match discarded, overlaps earlier DAG match here",
{OldRange});
if (Diags)
Diags->pop_back();
}
MatchPos = MI->End;
}

View File

@ -21,6 +21,7 @@
; ALIGN:Full input was:
; ALIGN-NEXT:<<<<<<
; ALIGN-NEXT: 1: hello world
; ALIGN-NEXT:check:1 ^~~~~
; ALIGN-NEXT:check:2'0 X~~~~
; ALIGN-NEXT: 2: goodbye
; ALIGN-NEXT:check:2'0 ~~~~~~~
@ -54,6 +55,7 @@
; CHK: <<<<<<
; CHK-NEXT: 1: hello
; CHK-V-NEXT: check:1 ^~~~~
; CHK-NEXT: 2: again
; CHK-NEXT: check:2'0 X~~~~
; CHK-NEXT: 3: whirled
@ -75,19 +77,22 @@
; RUN: echo 'CHECK-COUNT-3: pete' > %t.chk
; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \
; RUN: | FileCheck -match-full-lines %s -check-prefix=CNT
; RUN: | FileCheck -match-full-lines %s -check-prefixes=CNT,CNT-Q
; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \
; RUN: | FileCheck -match-full-lines %s -check-prefixes=CNT,CNT-V
; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \
; RUN: | FileCheck -match-full-lines %s -check-prefixes=CNT,CNT-V
; CNT: <<<<<<
; CNT-NEXT: 1: pete
; CNT-NEXT: 2: repete
; CNT-NEXT: 3: repeat
; CNT-NEXT: count:1 X~~~~~ error: no match found
; CNT-NEXT: >>>>>>
; CNT-NOT: {{.}}
; CNT: <<<<<<
; CNT-NEXT: 1: pete
; CNT-V-NEXT: count:1'0 ^~~~
; CNT-NEXT: 2: repete
; CNT-V-NEXT: count:1'1 ^~~~
; CNT-NEXT: 3: repeat
; CNT-Q-NEXT: count:1 X~~~~~ error: no match found
; CNT-V-NEXT: count:1'2 X~~~~~ error: no match found
; CNT-NEXT: >>>>>>
; CNT-NOT: {{.}}
;--------------------------------------------------
; CHECK-NEXT (also: EOF search-range, wrong-line match)
@ -110,10 +115,12 @@
; RUN: | FileCheck -match-full-lines %s -check-prefixes=NXT,NXT-V,NXT-VV
; NXT: <<<<<<
; NXT-NEXT: 1: hello
; NXT-NEXT: 2: again
; NXT-NEXT: 3:
; NXT-NEXT: next:3 X error: no match found
; NXT-NEXT: 1: hello
; NXT-V-NEXT: check:1 ^~~~~
; NXT-NEXT: 2: again
; NXT-V-NEXT: next:2 ^~~~~
; NXT-NEXT: 3:
; NXT-NEXT: next:3 X error: no match found
; NXT-NEXT: >>>>>>
; NXT-NOT: {{.}}
@ -135,7 +142,8 @@
; NXT2-NOT: {{.}}
;--------------------------------------------------
; CHECK-SAME (also: single-char search range, wrong-line match)
; CHECK-SAME (also: multiple annotations per line, single-char search range,
; wrong-line match)
;--------------------------------------------------
; Good match and no match.
@ -154,8 +162,10 @@
; RUN: | FileCheck -match-full-lines %s -check-prefixes=SAM,SAM-V,SAM-VV
; SAM: <<<<<<
; SAM-NEXT: 1: hello world!
; SAM-NEXT: same:3 X error: no match found
; SAM-NEXT: 1: hello world!
; SAM-V-NEXT: check:1 ^~~~~
; SAM-V-NEXT: same:2 ^~~~~
; SAM-NEXT: same:3 X error: no match found
; SAM-NEXT: >>>>>>
; SAM-NOT: {{.}}
@ -168,13 +178,16 @@
; SAM2: <<<<<<
; SAM2-NEXT: 1: hello world!
; SAM2-NEXT: check:1 ^~~~~
; SAM2-NEXT: same:2 ^~~~~
; SAM2-NEXT: 2: again
; SAM2-NEXT: same:3 !~~~~ error: match on wrong line
; SAM2-NEXT: >>>>>>
; SAM2-NOT: {{.}}
;--------------------------------------------------
; CHECK-EMPTY (also: search range ends at label, wrong-line match)
; CHECK-EMPTY (also: search range ends at label, single-char match, wrong-line
; match)
;--------------------------------------------------
; Good match and no match.
@ -203,11 +216,14 @@
; EMP: <<<<<<
; EMP-NEXT: 1: hello
; EMP-V-NEXT: check:1 ^~~~~
; EMP-NEXT: 2:
; EMP-V-NEXT: empty:2 ^
; EMP-NEXT: 3: world
; EMP-NEXT: empty:3 X~~~~
; EMP-NEXT: 4: label
; EMP-NEXT: empty:3 ~~~~~ error: no match found
; EMP-V-NEXT: label:4 ^~~~~
; EMP-NEXT: >>>>>>
; EMP-NOT: {{.}}
@ -228,6 +244,7 @@
; EMP2: <<<<<<
; EMP2-NEXT: 1: hello
; EMP2-V-NEXT: check:1 ^~~~~
; EMP2-NEXT: 2: world
; EMP2-NEXT: 3:
; EMP2-NEXT: empty:2 ! error: match on wrong line
@ -235,7 +252,7 @@
; EMP2-NOT: {{.}}
;--------------------------------------------------
; CHECK-NOT
; CHECK-NOT (also: EOF pattern)
;--------------------------------------------------
; No match (success) and unexpected match (error).
@ -259,6 +276,8 @@
; NOT-NEXT: 2: world
; NOT-NEXT: not:2 !~~~~ error: no match expected
; NOT-NEXT: 3: again
; NOT-VV-NEXT: 4:
; NOT-VV-NEXT: eof:2 ^
; NOT-NEXT: >>>>>>
; NOT-NOT: {{.}}
@ -278,11 +297,12 @@
; NOT2-NEXT: 2: world
; NOT2-NEXT: not:2 !~~~~ error: no match expected
; NOT2-NEXT: 3: again
; NOT2-V-NEXT: check:3 ^~~
; NOT2-NEXT: >>>>>>
; NOT2-NOT: {{.}}
;--------------------------------------------------
; CHECK-DAG
; CHECK-DAG (also: matches in different order than directives)
;--------------------------------------------------
; Good match, discarded match plus good match, and no match.
@ -297,22 +317,28 @@
; 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
; 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
; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG,DAG-V
; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \
; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG
; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG,DAG-V,DAG-VV
; DAG: <<<<<<
; DAG-NEXT: 1: abc
; DAG-NEXT: 2: def
; DAG-NEXT: 3: abc
; DAG-NEXT: dag:4 X~~ error: no match found
; DAG-NEXT: 1: abc
; DAG-V-NEXT: dag:2 ^~~
; DAG-NEXT: 2: def
; DAG-V-NEXT: dag:1 ^~~
; DAG-NEXT: 3: abc
; DAG-V-NEXT: dag:3 ^~~
; DAG-NEXT: dag:4 X~~ error: no match found
; 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.
@ -335,6 +361,8 @@
; LAB: <<<<<<
; LAB-NEXT: 1: lab0
; LAB-V-NEXT: label:1'0 ^~~~
; LAB-V-NEXT: label:1'1 ^~~~
; LAB-NEXT: 2: foo
; LAB-NEXT: label:3'0 X~~
; LAB-NEXT: 3: lab1

View File

@ -112,7 +112,9 @@ HELP-NOT: {{.}}
; CHECK-GOOD: Full input was:
; CHECK-GOOD-NEXT: <<<<<<
; CHECK-GOOD-NEXT: 1: ciao
; CHECK-GOOD-NEXT: check:1 ^~~~
; CHECK-GOOD-NEXT: 2: world
; CHECK-GOOD-NEXT: next:2 ^~~~~
; CHECK-GOOD-NEXT: >>>>>>
; CHECK-ERR: Full input was:

View File

@ -137,12 +137,15 @@ struct MarkerStyle {
/// A note to follow the marker, or empty string if none.
std::string Note;
MarkerStyle() {}
MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note)
MarkerStyle(char Lead, raw_ostream::Colors Color,
const std::string &Note = "")
: Lead(Lead), Color(Color), Note(Note) {}
};
static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) {
switch (MatchTy) {
case FileCheckDiag::MatchFinalAndExpected:
return MarkerStyle('^', raw_ostream::GREEN);
case FileCheckDiag::MatchFinalButExcluded:
return MarkerStyle('!', raw_ostream::RED, "error: no match expected");
case FileCheckDiag::MatchFinalButWrongLine:
@ -181,6 +184,9 @@ static void DumpInputAnnotationHelp(raw_ostream &OS) {
// Markers on annotation lines.
OS << " - ";
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~";
OS << " marks good match (reported if -v)\n"
<< " - ";
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~";
OS << " marks bad match, such as:\n"
<< " - CHECK-NEXT on same line as previous match (error)\n"
@ -195,9 +201,13 @@ static void DumpInputAnnotationHelp(raw_ostream &OS) {
// Colors.
OS << " - colors ";
WithColor(OS, raw_ostream::GREEN, true) << "success";
OS << ", ";
WithColor(OS, raw_ostream::RED, true) << "error";
OS << ", ";
WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match";
OS << ", ";
WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input";
OS << "\n\n"
<< "If you are not seeing color above or in input dumps, try: -color\n";
}
@ -222,6 +232,8 @@ struct InputAnnotation {
unsigned InputStartCol, InputEndCol;
/// The marker to use.
MarkerStyle Marker;
/// Whether this annotation represents a final match for an expected pattern.
bool FinalAndExpectedMatch;
};
/// Get an abbreviation for the check type.
@ -289,6 +301,8 @@ static void BuildInputAnnotations(const std::vector<FileCheckDiag> &Diags,
MarkerStyle Marker = GetMarker(DiagItr->MatchTy);
A.Marker = Marker;
A.FinalAndExpectedMatch =
DiagItr->MatchTy == FileCheckDiag::MatchFinalAndExpected;
// Compute the mark location, and break annotation into multiple
// annotations if it spans multiple lines.
@ -328,15 +342,17 @@ static void BuildInputAnnotations(const std::vector<FileCheckDiag> &Diags,
B.Marker.Note = "";
} else
B.InputEndCol = DiagItr->InputEndCol;
B.FinalAndExpectedMatch = A.FinalAndExpectedMatch;
Annotations.push_back(B);
}
}
}
}
static void DumpAnnotatedInput(
raw_ostream &OS, StringRef InputFileText,
std::vector<InputAnnotation> &Annotations, unsigned LabelWidth) {
static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
StringRef InputFileText,
std::vector<InputAnnotation> &Annotations,
unsigned LabelWidth) {
OS << "Full input was:\n<<<<<<\n";
// Sort annotations.
@ -359,9 +375,15 @@ static void DumpAnnotatedInput(
return A.InputLine < B.InputLine;
if (A.CheckLine != B.CheckLine)
return A.CheckLine < B.CheckLine;
assert(A.CheckDiagIndex != B.CheckDiagIndex &&
"expected diagnostic indices to be unique within a "
" check line");
// 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;
});
@ -393,14 +415,45 @@ static void DumpAnnotatedInput(
WithColor(OS, raw_ostream::BLACK, true)
<< format_decimal(Line, LabelWidth) << ": ";
// Print numbered line.
// 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::vector<InputAnnotation> FinalAndExpectedMatches;
if (Req.Verbose && WithColor(OS).colorsEnabled()) {
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;
while (InputFilePtr != InputFileEnd && !Newline) {
if (*InputFilePtr == '\n')
Newline = true;
else
OS << *InputFilePtr;
++InputFilePtr;
{
WithColor COS(OS);
bool InMatch = false;
if (Req.Verbose)
COS.changeColor(raw_ostream::CYAN, true, true);
for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) {
bool WasInMatch = InMatch;
InMatch = false;
for (auto M : FinalAndExpectedMatches) {
if (M.InputStartCol <= Col && Col < M.InputEndCol) {
InMatch = true;
break;
}
}
if (!WasInMatch && InMatch)
COS.resetColor();
else if (WasInMatch && !InMatch)
COS.changeColor(raw_ostream::CYAN, true, true);
if (*InputFilePtr == '\n')
Newline = true;
else
COS << *InputFilePtr;
++InputFilePtr;
}
}
OS << '\n';
unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline;
@ -561,7 +614,7 @@ int main(int argc, char **argv) {
std::vector<InputAnnotation> Annotations;
unsigned LabelWidth;
BuildInputAnnotations(Diags, Annotations, LabelWidth);
DumpAnnotatedInput(errs(), InputFileText, Annotations, LabelWidth);
DumpAnnotatedInput(errs(), Req, InputFileText, Annotations, LabelWidth);
}
return ExitCode;