-verify``. With this option FileCheck will verify that input does not contain
warnings not covered by any ``CHECK:`` patterns.
+.. option:: --dump-input <mode>
+
+ Dump input to stderr, adding annotations representing currently enabled
+ diagnostics. Do this either 'always', on 'fail', or 'never'. Specify 'help'
+ to explain the dump format and quit.
+
.. option:: --dump-input-on-failure
- When the check fails, dump all of the original input.
+ When the check fails, dump all of the original input. This option is
+ deprecated in favor of `--dump-input=fail`.
.. option:: --enable-var-scope
};
}
+struct FileCheckDiag;
+
class FileCheckPattern {
SMLoc PatternLoc;
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?
+ enum MatchType {
+ // TODO: More members will appear with later patches in this series.
+ /// Indicates no match for an expected pattern.
+ MatchNoneButExpected,
+ MatchTypeCount,
+ } MatchTy;
+ /// The search range.
+ unsigned InputStartLine, InputStartCol, InputEndLine, InputEndCol;
+ FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy,
+ SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange);
+};
+
//===----------------------------------------------------------------------===//
// Check Strings.
//===----------------------------------------------------------------------===//
size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode,
size_t &MatchLen, StringMap<StringRef> &VariableTable,
- FileCheckRequest &Req) const;
+ FileCheckRequest &Req, std::vector<FileCheckDiag> *Diags) const;
bool CheckNext(const SourceMgr &SM, StringRef Buffer) const;
bool CheckSame(const SourceMgr &SM, StringRef Buffer) const;
size_t CheckDag(const SourceMgr &SM, StringRef Buffer,
std::vector<const FileCheckPattern *> &NotStrings,
StringMap<StringRef> &VariableTable,
- const FileCheckRequest &Req) const;
+ const FileCheckRequest &Req,
+ std::vector<FileCheckDiag> *Diags) const;
};
/// FileCheck class takes the request and exposes various methods that
///
/// Returns false if the input fails to satisfy the checks.
bool CheckInput(SourceMgr &SM, StringRef Buffer,
- ArrayRef<FileCheckString> CheckStrings);
+ ArrayRef<FileCheckString> CheckStrings,
+ std::vector<FileCheckDiag> *Diags = nullptr);
};
} // namespace llvm
#endif
}
}
+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) {
+ 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);
+ return Range;
+}
+
void FileCheckPattern::PrintFuzzyMatch(
const SourceMgr &SM, StringRef Buffer,
const StringMap<StringRef> &VariableTable) const {
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 == '_');
}
StringRef Prefix, SMLoc Loc,
const FileCheckPattern &Pat, int MatchedCount,
StringRef Buffer, StringMap<StringRef> &VariableTable,
- bool VerboseVerbose) {
+ bool VerboseVerbose,
+ std::vector<FileCheckDiag> *Diags) {
if (!ExpectedMatch && !VerboseVerbose)
return;
// 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::MatchTypeCount,
+ 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);
static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
const FileCheckString &CheckStr, int MatchedCount,
StringRef Buffer, StringMap<StringRef> &VariableTable,
- bool VerboseVerbose) {
+ bool VerboseVerbose,
+ std::vector<FileCheckDiag> *Diags) {
PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat,
- MatchedCount, Buffer, VariableTable, VerboseVerbose);
+ MatchedCount, Buffer, VariableTable, VerboseVerbose, Diags);
}
/// Count the number of newlines in the specified range.
/// 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<StringRef> &VariableTable,
- FileCheckRequest &Req) const {
+ bool IsLabelScanMode, size_t &MatchLen,
+ StringMap<StringRef> &VariableTable,
+ FileCheckRequest &Req,
+ std::vector<FileCheckDiag> *Diags) const {
size_t LastPos = 0;
std::vector<const FileCheckPattern *> NotStrings;
// 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;
}
// report
if (MatchPos == StringRef::npos) {
PrintNoMatch(true, SM, *this, i, MatchBuffer, VariableTable,
- Req.VerboseVerbose);
+ Req.VerboseVerbose, Diags);
return StringRef::npos;
}
PrintMatch(true, SM, *this, i, MatchBuffer, VariableTable, MatchPos,
if (Pos == StringRef::npos) {
PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer,
- VariableTable, Req.VerboseVerbose);
+ VariableTable, Req.VerboseVerbose, nullptr);
continue;
}
}
/// Match "dag strings" and their mixed "not strings".
-size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
- std::vector<const FileCheckPattern *> &NotStrings,
- StringMap<StringRef> &VariableTable,
- const FileCheckRequest &Req) const {
+size_t
+FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
+ std::vector<const FileCheckPattern *> &NotStrings,
+ StringMap<StringRef> &VariableTable,
+ const FileCheckRequest &Req,
+ std::vector<FileCheckDiag> *Diags) const {
if (DagNotStrings.empty())
return 0;
// that group of CHECK-DAGs fails immediately.
if (MatchPosBuf == StringRef::npos) {
PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, 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.
///
/// Returns false if the input fails to satisfy the checks.
bool llvm::FileCheck::CheckInput(SourceMgr &SM, StringRef Buffer,
- ArrayRef<FileCheckString> CheckStrings) {
+ ArrayRef<FileCheckString> CheckStrings,
+ std::vector<FileCheckDiag> *Diags) {
bool ChecksFailed = false;
/// VariableTable - This holds all the current filecheck variables.
// 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;
// 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;
--- /dev/null
+;--------------------------------------------------
+; 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:check:2 X~~~~
+; ALIGN-NEXT: 2: goodbye
+; ALIGN-NEXT:check:2 ~~~~~~~
+; ALIGN-NEXT: 3: world
+; ALIGN-NEXT:check:2 ~~~~~ error: no match found
+; ALIGN-NEXT:>>>>>>
+; ALIGN-NOT:{{.}}
+
+;--------------------------------------------------
+; CHECK (also: multi-line search range)
+;--------------------------------------------------
+
+; 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-NEXT: 2: again
+; CHK-NEXT: check:2 X~~~~
+; CHK-NEXT: 3: whirled
+; CHK-NEXT: check:2 ~~~~~~~ error: no match found
+; CHK-NEXT: >>>>>>
+; CHK-NOT: {{.}}
+
+;--------------------------------------------------
+; CHECK-COUNT-<num>
+;--------------------------------------------------
+
+; Good match and no match.
+
+; RUN: echo 'pete' > %t.in
+; RUN: echo 'repete' >> %t.in
+; RUN: echo 'repeat' >> %t.in
+
+; 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: 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: {{.}}
+
+;--------------------------------------------------
+; CHECK-NEXT (also: EOF search-range)
+;--------------------------------------------------
+
+; 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-NEXT: 2: again
+; NXT-NEXT: 3:
+; NXT-NEXT: next:3 X error: no match found
+; NXT-NEXT: >>>>>>
+; NXT-NOT: {{.}}
+
+;--------------------------------------------------
+; CHECK-SAME (also: single-char search range)
+;--------------------------------------------------
+
+; 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-NEXT: same:3 X error: no match found
+; SAM-NEXT: >>>>>>
+; SAM-NOT: {{.}}
+
+;--------------------------------------------------
+; CHECK-EMPTY (also: search range ends at label)
+;--------------------------------------------------
+
+; 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-NEXT: 2:
+; EMP-NEXT: 3: world
+; EMP-NEXT: empty:3 X~~~~
+; EMP-NEXT: 4: label
+; EMP-NEXT: empty:3 ~~~~~ error: no match found
+; EMP-NEXT: >>>>>>
+; EMP-NOT: {{.}}
+
+;--------------------------------------------------
+; CHECK-DAG
+;--------------------------------------------------
+
+; 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
+; 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: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \
+; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG
+
+; DAG: <<<<<<
+; DAG-NEXT: 1: abc
+; DAG-NEXT: 2: def
+; DAG-NEXT: 3: abc
+; DAG-NEXT: dag:4 X~~ error: no match found
+; DAG-NEXT: >>>>>>
+; DAG-NOT: {{.}}
+
+;--------------------------------------------------
+; CHECK-LABEL
+;--------------------------------------------------
+
+; 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-NEXT: 2: foo
+; LAB-NEXT: label:3 X~~
+; LAB-NEXT: 3: lab1
+; LAB-NEXT: label:3 ~~~~
+; LAB-NEXT: 4: bar
+; LAB-NEXT: label:3 ~~~ error: no match found
+; LAB-NEXT: >>>>>>
+; LAB-NOT: {{.}}
+
+
--- /dev/null
+; 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
+
+; No positional arg.
+; RUN: not FileCheck -dump-input=foobar 2>&1 \
+; RUN: | FileCheck %s -match-full-lines -check-prefix=BADVAL
+
+BADVAL: FileCheck: for the -dump-input option: Cannot find option named 'foobar'!
+
+;--------------------------------------------------
+; help
+;--------------------------------------------------
+
+; Appended to normal command line.
+; RUN: FileCheck -input-file %t.err -color %t.check -dump-input=help \
+; RUN: | FileCheck %s -check-prefix=HELP
+
+; No positional arg.
+; RUN: FileCheck -dump-input=help | FileCheck %s -check-prefix=HELP
+
+HELP-NOT: {{.}}
+HELP: The following description was requested by -dump-input=help
+HELP: try{{.*}}-color
+HELP-NOT: {{.}}
+
+;--------------------------------------------------
+; 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
+
+;--------------------------------------------------
+; default: 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: 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
+
+;--------------------------------------------------
+; -dump-input-on-failure
+;--------------------------------------------------
+
+; RUN: FileCheck -input-file %t.good %t.check -check-prefix=CHECK \
+; RUN: -match-full-lines -dump-input-on-failure 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-on-failure 2>&1 \
+; RUN: | FileCheck %s -match-full-lines -check-prefix=CHECK-ERR
+
+; RUN: env FILECHECK_DUMP_INPUT_ON_FAILURE=1 \
+; 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: env FILECHECK_DUMP_INPUT_ON_FAILURE=1 \
+; 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
+
+; END.
+
+; CHECK-GOOD: Full input was:
+; CHECK-GOOD-NEXT: <<<<<<
+; CHECK-GOOD-NEXT: 1: ciao
+; CHECK-GOOD-NEXT: 2: world
+; CHECK-GOOD-NEXT: >>>>>>
+
+; CHECK-ERR: Full input was:
+; CHECK-ERR-NEXT: <<<<<<
+; CHECK-ERR-NEXT: 1: hello
+; CHECK-ERR-NEXT: check:1 X~~~~
+; CHECK-ERR-NEXT: 2: world
+; CHECK-ERR-NEXT: check:1 ~~~~~ error: no match found
+; CHECK-ERR-NEXT: >>>>>>
+
+; CHECK-NODUMP-NOT: <<<<<<
--- /dev/null
+; RUN: not FileCheck 2>&1 | FileCheck %s
+
+CHECK: <check-file> not specified
+++ /dev/null
-; 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: <<<<<<
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Process.h"
+#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/FileCheck.h"
using namespace llvm;
static cl::opt<std::string>
- CheckFilename(cl::Positional, cl::desc("<check-file>"), cl::Required);
+ CheckFilename(cl::Positional, cl::desc("<check-file>"), cl::Optional);
static cl::opt<std::string>
InputFilename("input-file", cl::desc("File to check (defaults to stdin)"),
"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"));
+ "FILECHECK_DUMP_INPUT_ON_FAILURE environment variable.\n"
+ "This option is deprecated in favor of -dump-input=fail.\n"));
+
+enum DumpInputValue {
+ DumpInputDefault,
+ DumpInputHelp,
+ DumpInputNever,
+ DumpInputFail,
+ DumpInputAlways
+};
+
+static cl::opt<DumpInputValue> DumpInput(
+ "dump-input", cl::init(DumpInputDefault),
+ cl::desc("Dump input to stderr, adding annotations representing\n"
+ " currently enabled diagnostics\n"),
+ cl::value_desc("mode"),
+ cl::values(clEnumValN(DumpInputHelp, "help",
+ "Explain dump format and quit"),
+ clEnumValN(DumpInputNever, "never", "Never dump input"),
+ clEnumValN(DumpInputFail, "fail", "Dump input on failure"),
+ clEnumValN(DumpInputAlways, "always", "Always dump input")));
typedef cl::list<std::string>::const_iterator prefix_iterator;
errs() << "\n";
}
+struct MarkerStyle {
+ /// The starting char (before tildes) for marking the line.
+ char Lead;
+ /// What color to use for this annotation.
+ raw_ostream::Colors Color;
+ /// 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)
+ : Lead(Lead), Color(Color), Note(Note) {}
+};
+
+static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) {
+ switch (MatchTy) {
+ case FileCheckDiag::MatchNoneButExpected:
+ return MarkerStyle('X', raw_ostream::RED, "error: no match found");
+ case FileCheckDiag::MatchTypeCount:
+ llvm_unreachable_internal("unexpected match type");
+ }
+ llvm_unreachable_internal("unexpected match type");
+}
+
+static void DumpInputAnnotationHelp(raw_ostream &OS) {
+ OS << "The following description was requested by -dump-input=help to\n"
+ << "explain the input annotations printed by -dump-input=always and\n"
+ << "-dump-input=fail:\n\n";
+
+ // Labels for input lines.
+ OS << " - ";
+ WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:";
+ OS << " labels line number L of the input file\n";
+
+ // Labels for annotation lines.
+ OS << " - ";
+ WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L";
+ OS << " labels the match result for a pattern of type T from "
+ << "line L of\n"
+ << " the check file\n";
+
+ // Markers on annotation lines.
+ OS << " - ";
+ WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~";
+ OS << " marks search range when no match is found\n";
+
+ // Colors.
+ OS << " - colors ";
+ WithColor(OS, raw_ostream::RED, true) << "error";
+ OS << "\n\n"
+ << "If you are not seeing color above or in input dumps, try: -color\n";
+}
+
+/// 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 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 marker to use.
+ MarkerStyle Marker;
+};
+
+/// Get an abbreviation for the check type.
+std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) {
+ switch (Ty) {
+ case Check::CheckPlain:
+ if (Ty.getCount() > 1)
+ return "count";
+ return "check";
+ case Check::CheckNext:
+ return "next";
+ case Check::CheckSame:
+ return "same";
+ case Check::CheckNot:
+ return "not";
+ case Check::CheckDAG:
+ return "dag";
+ case Check::CheckLabel:
+ return "label";
+ case Check::CheckEmpty:
+ return "empty";
+ case Check::CheckEOF:
+ return "eof";
+ case Check::CheckBadNot:
+ return "bad-not";
+ case Check::CheckBadCount:
+ return "bad-count";
+ case Check::CheckNone:
+ llvm_unreachable("invalid FileCheckType");
+ }
+ llvm_unreachable("unknown FileCheckType");
+}
+
+static void BuildInputAnnotations(const std::vector<FileCheckDiag> &Diags,
+ std::vector<InputAnnotation> &Annotations,
+ unsigned &LabelWidth) {
+ // What's the widest label?
+ LabelWidth = 0;
+ for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd;
+ ++DiagItr) {
+ InputAnnotation A;
+
+ // 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;
+ Label.flush();
+ LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size());
+
+ MarkerStyle Marker = GetMarker(DiagItr->MatchTy);
+ A.Marker = Marker;
+
+ // 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);
+ Annotations.push_back(A);
+ } else {
+ assert(DiagItr->InputStartLine < DiagItr->InputEndLine &&
+ "expected input range not to be inverted");
+ A.InputEndCol = UINT_MAX;
+ A.Marker.Note = "";
+ Annotations.push_back(A);
+ 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) {
+ Annotations.back().Marker.Note = Marker.Note;
+ break;
+ }
+ InputAnnotation B;
+ B.CheckLine = A.CheckLine;
+ B.Label = A.Label;
+ B.InputLine = L;
+ B.Marker = Marker;
+ B.Marker.Lead = '~';
+ B.InputStartCol = 1;
+ if (L != E) {
+ B.InputEndCol = UINT_MAX;
+ B.Marker.Note = "";
+ } else
+ B.InputEndCol = DiagItr->InputEndCol;
+ Annotations.push_back(B);
+ }
+ }
+ }
+}
+
+static void DumpAnnotatedInput(
+ raw_ostream &OS, StringRef InputFileText,
+ std::vector<InputAnnotation> &Annotations, unsigned LabelWidth) {
+ OS << "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). 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::sort(Annotations.begin(), Annotations.end(),
+ [](const InputAnnotation &A, const InputAnnotation &B) {
+ if (A.InputLine != B.InputLine)
+ return A.InputLine < B.InputLine;
+ return A.CheckLine < B.CheckLine;
+ });
+
+ // 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.
+ WithColor(OS, raw_ostream::BLACK, true)
+ << format_decimal(Line, LabelWidth) << ": ";
+
+ // Print numbered line.
+ bool Newline = false;
+ while (InputFilePtr != InputFileEnd && !Newline) {
+ if (*InputFilePtr == '\n')
+ Newline = true;
+ else
+ OS << *InputFilePtr;
+ ++InputFilePtr;
+ }
+ OS << '\n';
+ unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline;
+
+ // Print any annotations.
+ while (AnnotationItr != AnnotationEnd &&
+ AnnotationItr->InputLine == Line) {
+ WithColor COS(OS, AnnotationItr->Marker.Color, true);
+ // The two spaces below are where the ": " appears on input lines.
+ COS << left_justify(AnnotationItr->Label, LabelWidth) << " ";
+ unsigned Col;
+ for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col)
+ COS << ' ';
+ COS << AnnotationItr->Marker.Lead;
+ // If InputEndCol=UINT_MAX, stop at InputLineWidth.
+ for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth;
+ ++Col)
+ COS << '~';
+ const std::string &Note = AnnotationItr->Marker.Note;
+ if (!Note.empty()) {
+ // Put the note at the end of the input line. If we were to instead
+ // put the note right after the marker, subsequent annotations for the
+ // same input line might appear to mark this note instead of the input
+ // line.
+ for (; Col <= InputLineWidth; ++Col)
+ COS << ' ';
+ COS << ' ' << Note;
+ }
+ COS << '\n';
+ ++AnnotationItr;
+ }
+ }
+
+ OS << ">>>>>>\n";
+}
+
int main(int argc, char **argv) {
// Enable use of ANSI color codes because FileCheck is using them to
// highlight text.
InitLLVM X(argc, argv);
cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr,
"FILECHECK_OPTS");
+ if (DumpInput == DumpInputHelp) {
+ DumpInputAnnotationHelp(outs());
+ return 0;
+ }
+ if (CheckFilename.empty()) {
+ errs() << "<check-file> not specified\n";
+ return 2;
+ }
FileCheckRequest Req;
for (auto Prefix : CheckPrefixes)
return 2;
}
-
SourceMgr SM;
// Read the expected strings from the check file.
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";
+ if (DumpInput == DumpInputDefault)
+ DumpInput = DumpInputOnFailure ? DumpInputFail : DumpInputNever;
+
+ std::vector<FileCheckDiag> Diags;
+ int ExitCode = FC.CheckInput(SM, InputFileText, CheckStrings,
+ DumpInput == DumpInputNever ? nullptr : &Diags)
+ ? EXIT_SUCCESS
+ : 1;
+ if (DumpInput == DumpInputAlways ||
+ (ExitCode == 1 && DumpInput == DumpInputFail)) {
+ errs() << "\n"
+ << "Input file: "
+ << (InputFilename == "-" ? "<stdin>" : InputFilename.getValue())
+ << "\n"
+ << "Check file: " << CheckFilename << "\n"
+ << "\n"
+ << "-dump-input=help describes the format of the following dump.\n"
+ << "\n";
+ std::vector<InputAnnotation> Annotations;
+ unsigned LabelWidth;
+ BuildInputAnnotations(Diags, Annotations, LabelWidth);
+ DumpAnnotatedInput(errs(), InputFileText, Annotations, LabelWidth);
+ }
return ExitCode;
}