Skip to content

Commit

Permalink
[lit] Report line number for failed RUN command
Browse files Browse the repository at this point in the history
When debugging test failures with -vv (or -v in the case of the
internal shell), this makes it easier to locate the RUN line that
failed.  For example, clang's test/Driver/linux-ld.c has 892 total RUN
lines, and clang's test/Driver/arm-cortex-cpus.c has 424 RUN lines
after concatenation for line continuations.

When reading the generated shell script, this also makes it easier to
locate the RUN line that produced each command.

To support reporting RUN line numbers in the case of the internal
shell, this patch extends the internal shell to support the null
command, ":", except pipelines are not supported.

Reviewed By: asmith, delcypher

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

llvm-svn: 330755
  • Loading branch information
jdenny-ornl committed Apr 24, 2018
1 parent 080436f commit 8a47530
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 16 deletions.
2 changes: 2 additions & 0 deletions llvm/docs/CommandGuide/lit.rst
Expand Up @@ -85,6 +85,8 @@ OUTPUT OPTIONS
Echo all commands to stdout, as they are being executed.
This can be valuable for debugging test failures, as the last echoed command
will be the one which has failed.
To help you find the source RUN line, :program:`lit` inserts a no-op ``:``
command with argument ``'RUN: at line N'`` before each command pipeline.
This option implies ``--verbose``.

.. option:: -a, --show-all
Expand Down
31 changes: 22 additions & 9 deletions llvm/utils/lit/lit/TestRunner.py
Expand Up @@ -789,6 +789,13 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
results.append(cmdResult)
return cmdResult.exitCode

if cmd.commands[0].args[0] == ':':
if len(cmd.commands) != 1:
raise InternalShellError(cmd.commands[0], "Unsupported: ':' "
"cannot be part of a pipeline")
results.append(ShellCommandResult(cmd.commands[0], '', '', 0, False))
return 0;

procs = []
default_stdin = subprocess.PIPE
stderrTempFiles = []
Expand Down Expand Up @@ -1318,7 +1325,8 @@ def __init__(self, keyword, kind, parser=None, initial_value=None):
def parseLine(self, line_number, line):
try:
self.parsed_lines += [(line_number, line)]
self.value = self.parser(line_number, line, self.value)
self.value = self.parser(line_number, line, self.value,
self.keyword)
except ValueError as e:
raise ValueError(str(e) + ("\nin %s directive on test line %d" %
(self.keyword, line_number)))
Expand All @@ -1327,12 +1335,12 @@ def getValue(self):
return self.value

@staticmethod
def _handleTag(line_number, line, output):
def _handleTag(line_number, line, output, keyword):
"""A helper for parsing TAG type keywords"""
return (not line.strip() or output)

@staticmethod
def _handleCommand(line_number, line, output):
def _handleCommand(line_number, line, output, keyword):
"""A helper for parsing COMMAND type keywords"""
# Trim trailing whitespace.
line = line.rstrip()
Expand All @@ -1351,19 +1359,23 @@ def replace_line_number(match):
else:
if output is None:
output = []
line = ": '{keyword} at line {line}'; {real_command}".format(
keyword=keyword,
line=line_number,
real_command=line)
output.append(line)
return output

@staticmethod
def _handleList(line_number, line, output):
def _handleList(line_number, line, output, keyword):
"""A parser for LIST type keywords"""
if output is None:
output = []
output.extend([s.strip() for s in line.split(',')])
return output

@staticmethod
def _handleBooleanExpr(line_number, line, output):
def _handleBooleanExpr(line_number, line, output, keyword):
"""A parser for BOOLEAN_EXPR type keywords"""
if output is None:
output = []
Expand All @@ -1376,17 +1388,18 @@ def _handleBooleanExpr(line_number, line, output):
return output

@staticmethod
def _handleRequiresAny(line_number, line, output):
def _handleRequiresAny(line_number, line, output, keyword):
"""A custom parser to transform REQUIRES-ANY: into REQUIRES:"""

# Extract the conditions specified in REQUIRES-ANY: as written.
conditions = []
IntegratedTestKeywordParser._handleList(line_number, line, conditions)
IntegratedTestKeywordParser._handleList(line_number, line, conditions,
keyword)

# Output a `REQUIRES: a || b || c` expression in its place.
expression = ' || '.join(conditions)
IntegratedTestKeywordParser._handleBooleanExpr(line_number,
expression, output)
IntegratedTestKeywordParser._handleBooleanExpr(line_number, expression,
output, keyword)
return output

def parseIntegratedTestScript(test, additional_parsers=[],
Expand Down
@@ -0,0 +1,3 @@
# RUN: true
# RUN: false
# RUN: true
@@ -0,0 +1,11 @@
# RUN: : first line continued \
# RUN: to second line
# RUN: echo 'foo bar' \
# RUN: | FileCheck %s
# RUN: echo \
# RUN: 'foo baz' \
# RUN: | FileCheck %s
# RUN: echo 'foo bar' \
# RUN: | FileCheck %s

# CHECK: foo bar
@@ -0,0 +1,2 @@
import lit.formats
config.test_format = lit.formats.ShTest(execute_external=True)
@@ -0,0 +1,3 @@
# RUN: true
# RUN: false
# RUN: true
@@ -0,0 +1,11 @@
# RUN: : first line continued \
# RUN: to second line
# RUN: echo 'foo bar' \
# RUN: | FileCheck %s
# RUN: echo \
# RUN: 'foo baz' \
# RUN: | FileCheck %s
# RUN: echo 'foo bar' \
# RUN: | FileCheck %s

# CHECK: foo bar
@@ -0,0 +1,2 @@
import lit.formats
config.test_format = lit.formats.ShTest(execute_external=False)
3 changes: 3 additions & 0 deletions llvm/utils/lit/tests/Inputs/shtest-run-at-line/lit.cfg
@@ -0,0 +1,3 @@
import lit.formats
config.name = 'shtest-run-at-line'
config.suffixes = ['.txt']
3 changes: 3 additions & 0 deletions llvm/utils/lit/tests/Inputs/shtest-shell/colon-error.txt
@@ -0,0 +1,3 @@
# Check error on an unsupported ":". (cannot be part of a pipeline)
#
# RUN: : | echo "hello"
2 changes: 1 addition & 1 deletion llvm/utils/lit/tests/max-failures.py
Expand Up @@ -8,7 +8,7 @@
#
# END.

# CHECK: Failing Tests (26)
# CHECK: Failing Tests (27)
# CHECK: Failing Tests (1)
# CHECK: Failing Tests (2)
# CHECK: error: Setting --max-failures to 0 does not have any effect.
1 change: 1 addition & 0 deletions llvm/utils/lit/tests/shtest-format.py
Expand Up @@ -39,6 +39,7 @@
#
# CHECK: Command Output (stdout):
# CHECK-NEXT: --
# CHECK-NEXT: $ ":" "RUN: at line 1"
# CHECK-NEXT: $ "printf"
# CHECK-NEXT: # command output:
# CHECK-NEXT: line 1: failed test output on stdout
Expand Down
5 changes: 4 additions & 1 deletion llvm/utils/lit/tests/shtest-output-printing.py
Expand Up @@ -16,12 +16,15 @@
#
# CHECK: Command Output
# CHECK-NEXT: --
# CHECK-NEXT: $ ":" "RUN: at line 1"
# CHECK-NEXT: $ "true"
# CHECK-NEXT: $ ":" "RUN: at line 2"
# CHECK-NEXT: $ "echo" "hi"
# CHECK-NEXT: # command output:
# CHECK-NEXT: hi
#
# CHECK: $ "wc" "missing-file"
# CHECK: $ ":" "RUN: at line 3"
# CHECK-NEXT: $ "wc" "missing-file"
# CHECK-NEXT: # redirected output from '{{.*(/|\\\\)}}basic.txt.tmp.out':
# CHECK-NEXT: missing-file{{.*}} No such file or directory
# CHECK: note: command had no output on stdout or stderr
Expand Down
74 changes: 74 additions & 0 deletions llvm/utils/lit/tests/shtest-run-at-line.py
@@ -0,0 +1,74 @@
# Check that -vv makes the line number of the failing RUN command clear.
# (-v is actually sufficient in the case of the internal shell.)
#
# RUN: not %{lit} -j 1 -vv %{inputs}/shtest-run-at-line > %t.out
# RUN: FileCheck --input-file %t.out %s
#
# END.


# CHECK: Testing: 4 tests


# In the case of the external shell, we check for only RUN lines in stderr in
# case some shell implementations format "set -x" output differently.

# CHECK-LABEL: FAIL: shtest-run-at-line :: external-shell/basic.txt

# CHECK: Script:
# CHECK: : 'RUN: at line 1'; true
# CHECK-NEXT: : 'RUN: at line 2'; false
# CHECK-NEXT: : 'RUN: at line 3'; true

# CHECK: Command Output (stderr)
# CHECK: RUN: at line 1
# CHECK: RUN: at line 2
# CHECK-NOT: RUN

# CHECK-LABEL: FAIL: shtest-run-at-line :: external-shell/line-continuation.txt

# CHECK: Script:
# CHECK: : 'RUN: at line 1'; : first line continued to second line
# CHECK-NEXT: : 'RUN: at line 3'; echo 'foo bar' | FileCheck
# CHECK-NEXT: : 'RUN: at line 5'; echo 'foo baz' | FileCheck
# CHECK-NEXT: : 'RUN: at line 8'; echo 'foo bar' | FileCheck

# CHECK: Command Output (stderr)
# CHECK: RUN: at line 1
# CHECK: RUN: at line 3
# CHECK: RUN: at line 5
# CHECK-NOT: RUN


# CHECK-LABEL: FAIL: shtest-run-at-line :: internal-shell/basic.txt

# CHECK: Script:
# CHECK: : 'RUN: at line 1'; true
# CHECK-NEXT: : 'RUN: at line 2'; false
# CHECK-NEXT: : 'RUN: at line 3'; true

# CHECK: Command Output (stdout)
# CHECK: $ ":" "RUN: at line 1"
# CHECK-NEXT: $ "true"
# CHECK-NEXT: $ ":" "RUN: at line 2"
# CHECK-NEXT: $ "false"
# CHECK-NOT: RUN

# CHECK-LABEL: FAIL: shtest-run-at-line :: internal-shell/line-continuation.txt

# CHECK: Script:
# CHECK: : 'RUN: at line 1'; : first line continued to second line
# CHECK-NEXT: : 'RUN: at line 3'; echo 'foo bar' | FileCheck
# CHECK-NEXT: : 'RUN: at line 5'; echo 'foo baz' | FileCheck
# CHECK-NEXT: : 'RUN: at line 8'; echo 'foo bar' | FileCheck

# CHECK: Command Output (stdout)
# CHECK: $ ":" "RUN: at line 1"
# CHECK-NEXT: $ ":" "first" "line" "continued" "to" "second" "line"
# CHECK-NEXT: $ ":" "RUN: at line 3"
# CHECK-NEXT: $ "echo" "foo bar"
# CHECK-NEXT: $ "FileCheck" "{{.*}}"
# CHECK-NEXT: $ ":" "RUN: at line 5"
# CHECK-NEXT: $ "echo" "foo baz"
# CHECK-NEXT: $ "FileCheck" "{{.*}}"
# CHECK-NOT: RUN
12 changes: 10 additions & 2 deletions llvm/utils/lit/tests/shtest-shell.py
Expand Up @@ -26,6 +26,14 @@
# CHECK: error: command failed with exit status: 1
# CHECK: ***

# CHECK: FAIL: shtest-shell :: colon-error.txt
# CHECK: *** TEST 'shtest-shell :: colon-error.txt' FAILED ***
# CHECK: $ ":"
# CHECK: # command stderr:
# CHECK: Unsupported: ':' cannot be part of a pipeline
# CHECK: error: command failed with exit status: 127
# CHECK: ***

# CHECK: FAIL: shtest-shell :: diff-error-0.txt
# CHECK: *** TEST 'shtest-shell :: diff-error-0.txt' FAILED ***
# CHECK: $ "diff" "diff-error-0.txt" "diff-error-0.txt"
Expand Down Expand Up @@ -153,7 +161,7 @@
#
# CHECK: FAIL: shtest-shell :: error-1.txt
# CHECK: *** TEST 'shtest-shell :: error-1.txt' FAILED ***
# CHECK: shell parser error on: 'echo "missing quote'
# CHECK: shell parser error on: ': \'RUN: at line 3\'; echo "missing quote'
# CHECK: ***

# CHECK: FAIL: shtest-shell :: error-2.txt
Expand Down Expand Up @@ -219,4 +227,4 @@
# CHECK: PASS: shtest-shell :: sequencing-0.txt
# CHECK: XFAIL: shtest-shell :: sequencing-1.txt
# CHECK: PASS: shtest-shell :: valid-shell.txt
# CHECK: Failing Tests (26)
# CHECK: Failing Tests (27)
6 changes: 3 additions & 3 deletions llvm/utils/lit/tests/unit/TestRunner.py
Expand Up @@ -47,7 +47,7 @@ def load_keyword_parser_lit_tests():

@staticmethod
def make_parsers():
def custom_parse(line_number, line, output):
def custom_parse(line_number, line, output, keyword):
if output is None:
output = []
output += [part for part in line.split(' ') if part.strip()]
Expand Down Expand Up @@ -99,8 +99,8 @@ def test_commands(self):
cmd_parser = self.get_parser(parsers, 'MY_RUN:')
value = cmd_parser.getValue()
self.assertEqual(len(value), 2) # there are only two run lines
self.assertEqual(value[0].strip(), 'baz')
self.assertEqual(value[1].strip(), 'foo bar')
self.assertEqual(value[0].strip(), ": 'MY_RUN: at line 4'; baz")
self.assertEqual(value[1].strip(), ": 'MY_RUN: at line 7'; foo bar")

def test_custom(self):
parsers = self.make_parsers()
Expand Down

0 comments on commit 8a47530

Please sign in to comment.