Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/07-known-issues.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Known issues
============

Unintuitive behavior of CHECK-NOT
---------------------------------

A failing CHECK has a higher precedence than a failing CHECK-NOT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A failing `CHECK` has a higher precedence than a failing `CHECK-NOT` even if
`CHECK-NOT` appears first in the check file. If this happens, the output is
related to the failing `CHECK`, not the failing `CHECK-NOT`.

Input:

.. code-block:: text

String1
Stringggg2

Check file:

.. code-block:: text

; CHECK-NOT:String1
; CHECK:String2

Result:

.. code-block:: text

/Users/Stanislaw/.pyenv/shims/filecheck
filecheck.check:2:9: error: CHECK: expected string not found in input
; CHECK:String2
^
<stdin>:1:1: note: scanning from here
String1
^
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Welcome to FileCheck.py's documentation!
04-tutorial-llvm-lit-and-filecheck
05-check-commands
06-options
07-known-issues

.. Indices and tables
==================
Expand Down
104 changes: 85 additions & 19 deletions filecheck/FileCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,20 @@
__version__ = '0.0.5'


class CheckFailedException(Exception):
pass
class FailedCheck:
def __init__(self, check, line_idx):
self.check = check
self.line_idx = line_idx


class CheckFailedException(BaseException):
def __init__(self, failed_check):
self.failed_check = failed_check


class InputFinishedException(BaseException):
def __init__(self):
pass


class MatchType(Enum):
Expand Down Expand Up @@ -105,7 +117,8 @@ class CheckResult(Enum):
PASS = 1
FAIL_SKIP_LINE = 2
FAIL_FATAL = 3
CHECK_NOT_WITHOUT_MATCH = 4
CHECK_NOT_MATCH = 4
CHECK_NOT_WITHOUT_MATCH = 5


def check_line(line, current_check, match_full_lines):
Expand Down Expand Up @@ -142,13 +155,13 @@ def check_line(line, current_check, match_full_lines):
elif current_check.check_type == CheckType.CHECK_NOT:
if current_check.match_type == MatchType.SUBSTRING:
if current_check.expression in line:
return CheckResult.FAIL_FATAL
return CheckResult.CHECK_NOT_MATCH
else:
return CheckResult.CHECK_NOT_WITHOUT_MATCH

elif current_check.match_type == MatchType.REGEX:
if re.search(current_check.expression, line):
return CheckResult.FAIL_FATAL
return CheckResult.CHECK_NOT_MATCH
else:
return CheckResult.CHECK_NOT_WITHOUT_MATCH

Expand Down Expand Up @@ -246,7 +259,7 @@ def main():
checks.append(check)
continue

check_not_regex = "; {}-NOT: (.*)".format(check_prefix)
check_not_regex = "; {}-NOT:{}(.*)".format(check_prefix, strict_whitespace_match)
check_match = re.search(check_not_regex, line)
if check_match:
match_type = MatchType.SUBSTRING
Expand Down Expand Up @@ -291,6 +304,11 @@ def main():
check_iterator = iter(checks)

current_check = None
# This variable is currently only used for CHECK-NOT checks which do not
# necessarily fail with a last input line. So we have to keep the failing
# line index.
current_check_line_idx = None

try:
current_check = next(check_iterator)
except StopIteration:
Expand All @@ -315,6 +333,9 @@ def main():
exit(2)

try:
current_not_checks = []
failed_check = None

while True:
line = line.rstrip()
if not args.strict_whitespace:
Expand All @@ -323,9 +344,25 @@ def main():
input_lines.append(line)

while True:
if not failed_check:
for current_not_check in current_not_checks:
check_result = check_line(line,
current_not_check,
args.match_full_lines)
if check_result == CheckResult.CHECK_NOT_MATCH:
failed_check = FailedCheck(current_not_check, line_idx)

check_result = check_line(line, current_check, args.match_full_lines)

if check_result == CheckResult.PASS:
if check_result == CheckResult.FAIL_FATAL:
failed_check = FailedCheck(current_check, line_idx)
raise CheckFailedException(failed_check)

elif check_result == CheckResult.PASS:
if failed_check:
raise CheckFailedException(failed_check)

current_not_checks.clear()
try:
current_check = next(check_iterator)
except StopIteration:
Expand All @@ -336,27 +373,54 @@ def main():
current_scan_base = line_idx
break
except StopIteration:
raise CheckFailedException()
raise InputFinishedException

elif check_result == CheckResult.CHECK_NOT_MATCH:
failed_check = FailedCheck(current_check, line_idx)
try:
current_not_checks.append(current_check)
current_check = next(check_iterator)
continue
except StopIteration:
raise CheckFailedException(failed_check)

elif check_result == CheckResult.CHECK_NOT_WITHOUT_MATCH:
try:
current_not_checks.append(current_check)
current_check = next(check_iterator)
continue
except StopIteration:
exit(0)

elif check_result == CheckResult.FAIL_FATAL:
raise CheckFailedException()

elif check_result == CheckResult.FAIL_SKIP_LINE:
try:
line_idx, line = next(stdin_input_iter)
break
except StopIteration:
raise CheckFailedException()
else:
assert 0
except CheckFailedException:
pass
failed_check = FailedCheck(current_check, line_idx)
raise CheckFailedException(failed_check)

assert 0, "Should not reach here"
except InputFinishedException:
# We reach here if there is no input anymore and no check has failed so
# far. This means we can remove all CHECK-NOT checks from the input
# checks and see if there any other checks left.
if current_check.check_type == CheckType.CHECK_NOT:
still_actual_check = None
for check in check_iterator:
if check.check_type != CheckType.CHECK_NOT:
still_actual_check = check
break
else:
# No checks which are still actual have been found. Declare success.
exit(0)

current_check = still_actual_check
current_check_line_idx = line_idx

except CheckFailedException as e:
current_check = e.failed_check.check
current_check_line_idx = e.failed_check.line_idx

# CHECK-EMPTY is special: if there is no output anymore and this check is
# the 1) current and 2) the last one we want to declare success.
Expand Down Expand Up @@ -436,7 +500,8 @@ def main():
if current_check.check_type == CheckType.CHECK_NOT:
if (current_check.match_type == MatchType.SUBSTRING or
current_check.match_type == MatchType.REGEX):
last_read_line = input_lines[-1]
assert current_check_line_idx != None
last_read_line = input_lines[current_check_line_idx]

if not args.strict_whitespace:
last_read_line = re.sub("\\s+", ' ', last_read_line).strip()
Expand All @@ -448,15 +513,16 @@ def main():

print(current_check.source_line.rstrip())
print("^".rjust(current_check.start_index + 1))
print("<stdin>:{}:{}: note: found here".format(current_scan_base + 1, 1))
print("<stdin>:{}:{}: note: found here".format(current_check_line_idx + 1, 1))
print(last_read_line)

if current_check.match_type == MatchType.SUBSTRING:
match_pos = last_read_line.find(current_check.expression)
assert match_pos != -1

# TODO: check on lines which start with spaces
highlight_line = "^".rjust(match_pos, ' ')
print("^".ljust(len(current_check.expression), '~'))
print(highlight_line.ljust(len(current_check.expression), '~'))
else:
print("^".ljust(len(last_read_line), '~'))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; CHECK-NOT:{{.*String1.*}}
; CHECK:String2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NOT RELEVANT
String1
String2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
; RUN: cat %S/filecheck.input | (%FILECHECK_EXEC %S/filecheck.check 2>&1; test $? = 1;) | %FILECHECK_TESTER_EXEC %s --strict-whitespace --match-full-lines
; CHECK:{{^.*}}FileCheck{{(\.py)?$}}
; CHECK:{{^.*}}:1:13: error: CHECK-NOT: excluded string found in input
; CHECK:{{; CHECK-NOT:..\.\*String1.*..}}
; CHECK:{{^ \^$}}
; CHECK:<stdin>:2:1: note: found here
; CHECK:String1
; CHECK:^~~~~~~
; CHECK-EMPTY:
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; CHECK:String2
; CHECK-NOT:{{.*String1.*}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
String1
String2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
; RUN: cat %S/filecheck.input | (%FILECHECK_EXEC %S/filecheck.check 2>&1; test $? = 0;) | %FILECHECK_TESTER_EXEC %s --strict-whitespace --match-full-lines
; CHECK:{{^.*}}FileCheck{{(\.py)?$}}
; CHECK-EMPTY:
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
; CHECK:String2
; CHECK-NOT:{{.*String1.*}}
; CHECK:String3
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
String1
String2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
; RUN: cat %S/filecheck.input | (%FILECHECK_EXEC %S/filecheck.check 2>&1; test $? = 1;) | %FILECHECK_TESTER_EXEC %s --strict-whitespace --match-full-lines
; CHECK:{{^.*}}FileCheck{{(\.py)?$}}
; CHECK:{{^.*}}filecheck.check:3:9: error: CHECK: expected string not found in input{{$}}
; CHECK:; CHECK:String3
; CHECK: ^
; TODO: Without --match-full-lines, LLVM FileCheck allows multiple checks on a same line #52
; TODO: https://github.com/stanislaw/FileCheck.py/issues/52
; CHECK:{{<stdin>:(1|2):(8|1): note: scanning from here}}
; CHECK:{{.*}}
; CHECK:{{.*^}}
; TODO: "note: possible intended match here" feature: not clear when FileCheck decides to show it or not #63
; TODO: https://github.com/stanislaw/FileCheck.py/issues/63
; CHECK-EMPTY
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; CHECK-NOT:String1
; CHECK:String2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
String1
Stringggg2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
; RUN: cat %S/filecheck.input | (%FILECHECK_EXEC %S/filecheck.check 2>&1; test $? = 1;) | %FILECHECK_TESTER_EXEC %s --strict-whitespace --match-full-lines
; CHECK:{{^.*}}FileCheck{{(\.py)?$}}
; CHECK:{{^.*}}filecheck.check:2:9: error: CHECK: expected string not found in input
; CHECK:; CHECK:String2
; CHECK: ^
; CHECK:<stdin>:1:1: note: scanning from here
; CHECK:String1
; CHECK:^
; TODO: Difference in behavior: FileCheck.py prints possible intended match,
; TODO: while FileCheck C++ does not.
; CHECK-EMPTY
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; CHECK-NOT:{{.*String2.*}}
; CHECK:String3
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
String1
String2
Stringggg3
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
; RUN: cat %S/filecheck.input | (%FILECHECK_EXEC %S/filecheck.check 2>&1; test $? = 1;) | %FILECHECK_TESTER_EXEC %s --strict-whitespace --match-full-lines
; CHECK:{{^.*}}FileCheck{{(\.py)?$}}
; CHECK:{{^.*}}:2:9: error: CHECK: expected string not found in input
; CHECK:; CHECK:String3
; CHECK: ^
; CHECK:<stdin>:1:1: note: scanning from here
; CHECK:String1
; CHECK:^
; TODO: Difference in behavior: FileCheck.py prints possible intended match,
; TODO: while FileCheck C++ does not.
; CHECK-EMPTY