Skip to content

Commit 080fb3e

Browse files
authored
[lit] Clean up internal shell parse errors with ScriptFatal (#68496)
Without this patch, the functions `executeScriptInternal` and thus `runOnce` in `llvm/utils/lit/lit/TestRunner.py` return either a tuple like `(out, err, exitCode, timeoutInfo)` or a `lit.Test.Result` object. They return the latter only when there's a lit internal shell parse error in a RUN line. In my opinion, a more straight-forward way to handle exceptional cases like that is to use python exceptions. For that purpose, this patch introduces `ScriptFatal`. Thus, this patch changes `executeScriptInternal` to always either return the tuple or raise the `ScriptFatal` exception. It updates `runOnce` and `libcxx/utils/libcxx/test/format.py` to catch the exception rather than check for the special return type. This patch also changes `runOnce` to convert the exception to a `Test.UNRESOLVED` result instead of `TEST.FAIL`. The former is the proper result for such a malformed test, for which a rerun (given an `ALLOW_RETRIES:`) serves no purpose. There are at least two benefits from this change. First, `_runShTest` no longer has to specially and cryptically handle this case to avoid unnecessary reruns. Second, an `XFAIL:` directive no longer hides such a failure [as we saw previously](https://reviews.llvm.org/D154987#4501125). To facilitate the `_runShTest` change, this patch inserts the internal shell parse error diagnostic into the format of the test's normal debug output rather than suppressing the latter entirely. That change is also important for [D154987](https://reviews.llvm.org/D154987), which proposes to reuse `ScriptFatal` for python compile errors in PYTHON lines or in `config.prologue`. In that case, the diagnostic might follow debugging output from the test's previous RUN or PYTHON lines, so suppressing the normal debug output would lose information.
1 parent 6795bfc commit 080fb3e

File tree

3 files changed

+55
-26
lines changed

3 files changed

+55
-26
lines changed

libcxx/utils/libcxx/test/format.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ def _executeScriptInternal(test, litConfig, commands):
4545

4646
_, tmpBase = _getTempPaths(test)
4747
execDir = os.path.dirname(test.getExecPath())
48-
res = lit.TestRunner.executeScriptInternal(
49-
test, litConfig, tmpBase, parsedCommands, execDir, debug=False
50-
)
51-
if isinstance(res, lit.Test.Result): # Handle failure to parse the Lit test
52-
res = ("", res.output, 127, None)
48+
try:
49+
res = lit.TestRunner.executeScriptInternal(
50+
test, litConfig, tmpBase, parsedCommands, execDir, debug=False
51+
)
52+
except lit.TestRunner.ScriptFatal as e:
53+
res = ("", str(e), 127, None)
5354
(out, err, exitCode, timeoutInfo) = res
5455

5556
return (out, err, exitCode, timeoutInfo, parsedCommands)

llvm/utils/lit/lit/TestRunner.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import shutil
1313
import tempfile
1414
import threading
15+
import typing
16+
from typing import Optional, Tuple
1517

1618
import io
1719

@@ -34,6 +36,17 @@ def __init__(self, command, message):
3436
self.message = message
3537

3638

39+
class ScriptFatal(Exception):
40+
"""
41+
A script had a fatal error such that there's no point in retrying. The
42+
message has not been emitted on stdout or stderr but is instead included in
43+
this exception.
44+
"""
45+
46+
def __init__(self, message):
47+
super().__init__(message)
48+
49+
3750
kIsWindows = platform.system() == "Windows"
3851

3952
# Don't use close_fds on Windows.
@@ -1009,15 +1022,18 @@ def formatOutput(title, data, limit=None):
10091022
return out
10101023

10111024

1012-
# Normally returns out, err, exitCode, timeoutInfo.
1025+
# Always either returns the tuple (out, err, exitCode, timeoutInfo) or raises a
1026+
# ScriptFatal exception.
10131027
#
10141028
# If debug is True (the normal lit behavior), err is empty, and out contains an
10151029
# execution trace, including stdout and stderr shown per command executed.
10161030
#
10171031
# If debug is False (set by some custom lit test formats that call this
10181032
# function), out contains only stdout from the script, err contains only stderr
10191033
# from the script, and there is no execution trace.
1020-
def executeScriptInternal(test, litConfig, tmpBase, commands, cwd, debug=True):
1034+
def executeScriptInternal(
1035+
test, litConfig, tmpBase, commands, cwd, debug=True
1036+
) -> Tuple[str, str, int, Optional[str]]:
10211037
cmds = []
10221038
for i, ln in enumerate(commands):
10231039
# Within lit, we try to always add '%dbg(...)' to command lines in order
@@ -1043,9 +1059,9 @@ def executeScriptInternal(test, litConfig, tmpBase, commands, cwd, debug=True):
10431059
ShUtil.ShParser(ln, litConfig.isWindows, test.config.pipefail).parse()
10441060
)
10451061
except:
1046-
return lit.Test.Result(
1047-
Test.FAIL, f"shell parser error on {dbg}: {command.lstrip()}\n"
1048-
)
1062+
raise ScriptFatal(
1063+
f"shell parser error on {dbg}: {command.lstrip()}\n"
1064+
) from None
10491065

10501066
cmd = cmds[0]
10511067
for c in cmds[1:]:
@@ -2130,8 +2146,11 @@ def parseIntegratedTestScript(test, additional_parsers=[], require_script=True):
21302146
return script
21312147

21322148

2133-
def _runShTest(test, litConfig, useExternalSh, script, tmpBase):
2134-
def runOnce(execdir):
2149+
def _runShTest(test, litConfig, useExternalSh, script, tmpBase) -> lit.Test.Result:
2150+
# Always returns the tuple (out, err, exitCode, timeoutInfo, status).
2151+
def runOnce(
2152+
execdir,
2153+
) -> Tuple[str, str, int, Optional[str], Test.ResultCode]:
21352154
# script is modified below (for litConfig.per_test_coverage, and for
21362155
# %dbg expansions). runOnce can be called multiple times, but applying
21372156
# the modifications multiple times can corrupt script, so always modify
@@ -2158,12 +2177,16 @@ def runOnce(execdir):
21582177
command = buildPdbgCommand(dbg, command)
21592178
scriptCopy[i] = command
21602179

2161-
if useExternalSh:
2162-
res = executeScript(test, litConfig, tmpBase, scriptCopy, execdir)
2163-
else:
2164-
res = executeScriptInternal(test, litConfig, tmpBase, scriptCopy, execdir)
2165-
if isinstance(res, lit.Test.Result):
2166-
return res
2180+
try:
2181+
if useExternalSh:
2182+
res = executeScript(test, litConfig, tmpBase, scriptCopy, execdir)
2183+
else:
2184+
res = executeScriptInternal(
2185+
test, litConfig, tmpBase, scriptCopy, execdir
2186+
)
2187+
except ScriptFatal as e:
2188+
out = f"# " + "\n# ".join(str(e).splitlines()) + "\n"
2189+
return out, "", 1, None, Test.UNRESOLVED
21672190

21682191
out, err, exitCode, timeoutInfo = res
21692192
if exitCode == 0:
@@ -2183,9 +2206,6 @@ def runOnce(execdir):
21832206
attempts = test.allowed_retries + 1
21842207
for i in range(attempts):
21852208
res = runOnce(execdir)
2186-
if isinstance(res, lit.Test.Result):
2187-
return res
2188-
21892209
out, err, exitCode, timeoutInfo, status = res
21902210
if status != Test.FAIL:
21912211
break

llvm/utils/lit/tests/shtest-shell.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -561,10 +561,17 @@
561561

562562
# FIXME: The output here sucks.
563563
#
564-
# CHECK: FAIL: shtest-shell :: error-1.txt
565-
# CHECK: *** TEST 'shtest-shell :: error-1.txt' FAILED ***
566-
# CHECK: shell parser error on RUN: at line 3: echo "missing quote
567-
# CHECK: ***
564+
# CHECK: UNRESOLVED: shtest-shell :: error-1.txt
565+
# CHECK-NEXT: *** TEST 'shtest-shell :: error-1.txt' FAILED ***
566+
# CHECK-NEXT: Exit Code: 1
567+
# CHECK-EMPTY:
568+
# CHECK-NEXT: Command Output (stdout):
569+
# CHECK-NEXT: --
570+
# CHECK-NEXT: # shell parser error on RUN: at line 3: echo "missing quote
571+
# CHECK-EMPTY:
572+
# CHECK-NEXT: --
573+
# CHECK-EMPTY:
574+
# CHECK-NEXT: ***
568575

569576
# CHECK: FAIL: shtest-shell :: error-2.txt
570577
# CHECK: *** TEST 'shtest-shell :: error-2.txt' FAILED ***
@@ -643,4 +650,5 @@
643650
# CHECK: ***
644651

645652
# CHECK: PASS: shtest-shell :: valid-shell.txt
646-
# CHECK: Failed Tests (39)
653+
# CHECK: Unresolved Tests (1)
654+
# CHECK: Failed Tests (38)

0 commit comments

Comments
 (0)