Skip to content

Commit dbf77e4

Browse files
authored
[utils] revamp options controlling lit's output (#167192)
Lit has a number of options controlling the output, but they don't compose very well. This breaks the existing options down into smaller, orthogonal options, and makes the existing options aliases of the new ones. This introduces the following options: --test-output {off,failed,all} --print-result-after {off,failed,all} --diagnostic-level {error,warning,note} --terse-summary --no-terse-summary --progress-bar (mirroring --no-progress-bar) --test-output and --print-result-after are not entirely orthogonal, as '--test-output X' requires that --print-result-after is set to at least X, and implicitly does so if it isn't already. Conversely, '--print-result-after Y' requires that --test-output is at most Y, and implicitly lowers if it is higher. This means that the following invocations have different end results, as they are applied in order: '--test-output all --print-result-after off' '--print-result-after off --test-output all' The following existing options are now aliases as follows: -q, --quiet '--diagnostic-level error --test-output off --terse-summary' -s, --succinct '--progress-bar --print-result-after failed' -v, --verbose '--test-output failed' -a, --show-all '--test-output all' These where all completely separate options and would override each other in ad-hoc ways, with no regard to the order they were given. This fixes #106643 This is based on the RFC https://discourse.llvm.org/t/rfc-new-command-line-options-for-controlling-llvm-lit-output/ with the addition of --terse-summary, which was a behaviour of -q that was not captured by the original RFC. This also diverges from the RFC in that --debug is NOT folded into --diagnostic-level, because it can be useful to debug any configuration, including those specifying --diagnostic-level. Example combination that is possible now but wasn't before: '--diagnostic-level error --test-output all --progress-bar' Another use case is aliases, where you can alias e.g: alias lit=llvm-lit --quiet but still override the specified default options.
1 parent 4b05581 commit dbf77e4

36 files changed

+1387
-87
lines changed

compiler-rt/test/lit.common.cfg.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,14 @@ def push_dynamic_library_lookup_path(config, new_path):
195195
# Normalize the path for comparison
196196
if test_cc_resource_dir is not None:
197197
test_cc_resource_dir = os.path.realpath(test_cc_resource_dir)
198-
if lit_config.debug:
199-
lit_config.note(f"Resource dir for {config.clang} is {test_cc_resource_dir}")
198+
lit_config.dbg(f"Resource dir for {config.clang} is {test_cc_resource_dir}")
200199
local_build_resource_dir = os.path.realpath(config.compiler_rt_output_dir)
201200
if test_cc_resource_dir != local_build_resource_dir and config.test_standalone_build_libs:
202201
if config.compiler_id == "Clang":
203-
if lit_config.debug:
204-
lit_config.note(
205-
f"Overriding test compiler resource dir to use "
206-
f'libraries in "{config.compiler_rt_libdir}"'
207-
)
202+
lit_config.dbg(
203+
f"Overriding test compiler resource dir to use "
204+
f'libraries in "{config.compiler_rt_libdir}"'
205+
)
208206
# Ensure that we use the just-built static libraries when linking by
209207
# overriding the Clang resource directory. Additionally, we want to use
210208
# the builtin headers shipped with clang (e.g. stdint.h), so we

libcxx/test/selftest/dsl/dsl.sh.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def setUp(self):
6161
self.litConfig = lit.LitConfig.LitConfig(
6262
progname="lit",
6363
path=[],
64-
quiet=False,
64+
diagnostic_level="note",
6565
useValgrind=False,
6666
valgrindLeakCheck=False,
6767
valgrindArgs=[],

libcxx/utils/libcxx/test/config.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def _appendToSubstitution(substitutions, key, value):
2222

2323
def configure(parameters, features, config, lit_config):
2424
note = lambda s: lit_config.note("({}) {}".format(config.name, s))
25+
debug = lambda s: lit_config.dbg("({}) {}".format(config.name, s))
2526
config.environment = dict(os.environ)
2627

2728
# Apply the actions supplied by parameters to the configuration first, since
@@ -31,25 +32,23 @@ def configure(parameters, features, config, lit_config):
3132
actions = param.getActions(config, lit_config.params)
3233
for action in actions:
3334
action.applyTo(config)
34-
if lit_config.debug:
35-
note(
36-
"Applied '{}' as a result of parameter '{}'".format(
37-
action.pretty(config, lit_config.params),
38-
param.pretty(config, lit_config.params),
39-
)
35+
debug(
36+
"Applied '{}' as a result of parameter '{}'".format(
37+
action.pretty(config, lit_config.params),
38+
param.pretty(config, lit_config.params),
4039
)
40+
)
4141

4242
# Then, apply the automatically-detected features.
4343
for feature in features:
4444
actions = feature.getActions(config)
4545
for action in actions:
4646
action.applyTo(config)
47-
if lit_config.debug:
48-
note(
49-
"Applied '{}' as a result of implicitly detected feature '{}'".format(
50-
action.pretty(config, lit_config.params), feature.pretty(config)
51-
)
47+
debug(
48+
"Applied '{}' as a result of implicitly detected feature '{}'".format(
49+
action.pretty(config, lit_config.params), feature.pretty(config)
5250
)
51+
)
5352

5453
# Print the basic substitutions
5554
for sub in ("%{cxx}", "%{flags}", "%{compile_flags}", "%{link_flags}", "%{benchmark_flags}", "%{exec}"):

libcxx/utils/libcxx/test/dsl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def _executeWithFakeConfig(test, commands):
8888
litConfig = lit.LitConfig.LitConfig(
8989
progname="lit",
9090
path=[],
91-
quiet=False,
91+
diagnostic_level="note",
9292
useValgrind=False,
9393
valgrindLeakCheck=False,
9494
valgrindArgs=[],

llvm/utils/lit/lit/LitConfig.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import
22
import inspect
33
import os
4+
import enum
45
import platform
56
import sys
67

@@ -25,7 +26,7 @@ def __init__(
2526
self,
2627
progname,
2728
path,
28-
quiet,
29+
diagnostic_level,
2930
useValgrind,
3031
valgrindLeakCheck,
3132
valgrindArgs,
@@ -46,7 +47,7 @@ def __init__(
4647
self.progname = progname
4748
# The items to add to the PATH environment variable.
4849
self.path = [str(p) for p in path]
49-
self.quiet = bool(quiet)
50+
self.diagnostic_level = diagnostic_level
5051
self.useValgrind = bool(useValgrind)
5152
self.valgrindLeakCheck = bool(valgrindLeakCheck)
5253
self.valgrindUserArgs = list(valgrindArgs)
@@ -155,8 +156,7 @@ def per_test_coverage(self, value):
155156
def load_config(self, config, path):
156157
"""load_config(config, path) - Load a config object from an alternate
157158
path."""
158-
if self.debug:
159-
self.note("load_config from %r" % path)
159+
self.dbg("load_config from %r" % path)
160160
config.load_from_path(path, self)
161161
return config
162162

@@ -209,6 +209,8 @@ def getToolsPath(self, dir, paths, tools):
209209
return dir
210210

211211
def _write_message(self, kind, message):
212+
if not self.diagnostic_level_enabled(kind):
213+
return
212214
# Get the file/line where this message was generated.
213215
f = inspect.currentframe()
214216
# Step out of _write_message, and then out of wrapper.
@@ -234,13 +236,21 @@ def substitute(self, string):
234236
"unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)
235237
)
236238

239+
def diagnostic_level_enabled(self, kind):
240+
if kind == "debug":
241+
return self.debug
242+
return DiagnosticLevel.create(self.diagnostic_level) >= DiagnosticLevel.create(
243+
kind
244+
)
245+
246+
def dbg(self, message):
247+
self._write_message("debug", message)
248+
237249
def note(self, message):
238-
if not self.quiet:
239-
self._write_message("note", message)
250+
self._write_message("note", message)
240251

241252
def warning(self, message):
242-
if not self.quiet:
243-
self._write_message("warning", message)
253+
self._write_message("warning", message)
244254
self.numWarnings += 1
245255

246256
def error(self, message):
@@ -250,3 +260,25 @@ def error(self, message):
250260
def fatal(self, message):
251261
self._write_message("fatal", message)
252262
sys.exit(2)
263+
264+
265+
@enum.unique
266+
class DiagnosticLevel(enum.IntEnum):
267+
FATAL = 0
268+
ERROR = 1
269+
WARNING = 2
270+
NOTE = 3
271+
272+
@classmethod
273+
def create(cls, value):
274+
if value == "fatal":
275+
return cls.FATAL
276+
if value == "error":
277+
return cls.ERROR
278+
if value == "warning":
279+
return cls.WARNING
280+
if value == "note":
281+
return cls.NOTE
282+
raise ValueError(
283+
f"invalid diagnostic level {repr(value)} of type {type(value)}"
284+
)

llvm/utils/lit/lit/LitTestCase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def load_test_suite(inputs):
4646
lit_config = lit.LitConfig.LitConfig(
4747
progname="lit",
4848
path=[],
49-
quiet=False,
49+
diagnostic_level="note",
5050
useValgrind=False,
5151
valgrindLeakCheck=False,
5252
valgrindArgs=[],

llvm/utils/lit/lit/TestingConfig.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ def load_from_path(self, path, litConfig):
143143
cfg_globals["__file__"] = path
144144
try:
145145
exec(compile(data, path, "exec"), cfg_globals, None)
146-
if litConfig.debug:
147-
litConfig.note("... loaded config %r" % path)
146+
litConfig.dbg("... loaded config %r" % path)
148147
except SystemExit:
149148
e = sys.exc_info()[1]
150149
# We allow normal system exit inside a config file to just

llvm/utils/lit/lit/cl_arguments.py

Lines changed: 137 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,59 @@ class TestOrder(enum.Enum):
1515
SMART = "smart"
1616

1717

18+
@enum.unique
19+
class TestOutputLevel(enum.IntEnum):
20+
OFF = 0
21+
FAILED = 1
22+
ALL = 2
23+
24+
@classmethod
25+
def create(cls, value):
26+
if value == "off":
27+
return cls.OFF
28+
if value == "failed":
29+
return cls.FAILED
30+
if value == "all":
31+
return cls.ALL
32+
raise ValueError(f"invalid output level {repr(value)} of type {type(value)}")
33+
34+
35+
class TestOutputAction(argparse.Action):
36+
def __init__(self, option_strings, dest, **kwargs):
37+
super().__init__(option_strings, dest, nargs=None, **kwargs)
38+
39+
def __call__(self, parser, namespace, value, option_string=None):
40+
TestOutputAction.setOutputLevel(namespace, self.dest, value)
41+
42+
@classmethod
43+
def setOutputLevel(cls, namespace, dest, value):
44+
setattr(namespace, dest, value)
45+
if dest == "test_output" and TestOutputLevel.create(
46+
namespace.print_result_after
47+
) < TestOutputLevel.create(value):
48+
setattr(namespace, "print_result_after", value)
49+
elif dest == "print_result_after" and TestOutputLevel.create(
50+
namespace.test_output
51+
) > TestOutputLevel.create(value):
52+
setattr(namespace, "test_output", value)
53+
54+
55+
class AliasAction(argparse.Action):
56+
def __init__(self, option_strings, dest, nargs=None, **kwargs):
57+
self.expansion = kwargs.pop("alias", None)
58+
if not self.expansion:
59+
raise ValueError("no aliases expansion provided")
60+
super().__init__(option_strings, dest, nargs=0, **kwargs)
61+
62+
def __call__(self, parser, namespace, value, option_string=None):
63+
for e in self.expansion:
64+
if callable(e):
65+
e(namespace)
66+
else:
67+
dest, val = e
68+
setattr(namespace, dest, val)
69+
70+
1871
def parse_args():
1972
parser = argparse.ArgumentParser(prog="lit", fromfile_prefix_chars="@")
2073
parser.add_argument(
@@ -55,41 +108,103 @@ def parse_args():
55108
)
56109

57110
format_group = parser.add_argument_group("Output Format")
58-
# FIXME: I find these names very confusing, although I like the
59-
# functionality.
60111
format_group.add_argument(
61-
"-q", "--quiet", help="Suppress no error output", action="store_true"
112+
"--test-output",
113+
help="Control whether the executed commands and their outputs are printed after each test has executed (default off). "
114+
"If --print-result-after is set lower than the level given to --test-output, --print-result-after is raised to match.",
115+
choices=["off", "failed", "all"],
116+
default="off",
117+
action=TestOutputAction,
118+
)
119+
format_group.add_argument(
120+
"--print-result-after",
121+
help="Control which the executed test names and results are printed after each test has executed (default all). "
122+
"If --test-output is set higher than the level given to --print-result-after, --test-output is lowered to match.",
123+
choices=["off", "failed", "all"],
124+
default="all",
125+
action=TestOutputAction,
126+
)
127+
format_group.add_argument(
128+
"--diagnostic-level",
129+
help="Control how verbose lit diagnostics should be (default note)",
130+
choices=["error", "warning", "note"],
131+
default="note",
132+
)
133+
format_group.add_argument(
134+
"--terse-summary",
135+
help="Print the elapsed time and the number of passed tests after all tests have finished (default on)",
136+
action="store_true",
137+
dest="terse_summary",
138+
)
139+
format_group.add_argument(
140+
"--no-terse-summary",
141+
help="Don't show the elapsed time after all tests have finished, and only show the number of failed tests.",
142+
action="store_false",
143+
dest="terse_summary",
144+
)
145+
parser.set_defaults(terse_summary=False)
146+
format_group.add_argument(
147+
"-q",
148+
"--quiet",
149+
help="Alias for '--diagnostic-level=error --test-output=off --terse-summary'",
150+
action=AliasAction,
151+
alias=[
152+
lambda namespace: TestOutputAction.setOutputLevel(
153+
namespace, "print_result_after", "failed"
154+
),
155+
lambda namespace: TestOutputAction.setOutputLevel(
156+
namespace, "test_output", "off"
157+
),
158+
("diagnostic_level", "error"),
159+
("terse_summary", True),
160+
],
62161
)
63162
format_group.add_argument(
64163
"-s",
65164
"--succinct",
66-
help="Reduce amount of output."
67-
" Additionally, show a progress bar,"
68-
" unless --no-progress-bar is specified.",
69-
action="store_true",
165+
help="Alias for '--progress-bar --print-result-after=failed'",
166+
action=AliasAction,
167+
alias=[
168+
("useProgressBar", True),
169+
lambda namespace: TestOutputAction.setOutputLevel(
170+
namespace, "print_result_after", "failed"
171+
),
172+
],
70173
)
71174
format_group.add_argument(
72175
"-v",
73176
"--verbose",
74-
dest="showOutput",
75177
help="For failed tests, show all output. For example, each command is"
76178
" printed before it is executed, so the last printed command is the one"
77-
" that failed.",
78-
action="store_true",
179+
" that failed. Alias for '--test-output=failed'",
180+
action=AliasAction,
181+
alias=[
182+
lambda namespace: TestOutputAction.setOutputLevel(
183+
namespace, "test_output", "failed"
184+
),
185+
],
79186
)
80187
format_group.add_argument(
81188
"-vv",
82189
"--echo-all-commands",
83-
dest="showOutput",
84190
help="Deprecated alias for -v.",
85-
action="store_true",
191+
action=AliasAction,
192+
alias=[
193+
lambda namespace: TestOutputAction.setOutputLevel(
194+
namespace, "test_output", "failed"
195+
),
196+
],
86197
)
87198
format_group.add_argument(
88199
"-a",
89200
"--show-all",
90-
dest="showAllOutput",
91-
help="Enable -v, but for all tests not just failed tests.",
92-
action="store_true",
201+
help="Enable -v, but for all tests not just failed tests. Alias for '--test-output=all'",
202+
action=AliasAction,
203+
alias=[
204+
lambda namespace: TestOutputAction.setOutputLevel(
205+
namespace, "test_output", "all"
206+
),
207+
],
93208
)
94209
format_group.add_argument(
95210
"-r",
@@ -105,10 +220,16 @@ def parse_args():
105220
help="Write test results to the provided path",
106221
metavar="PATH",
107222
)
223+
format_group.add_argument(
224+
"--progress-bar",
225+
dest="useProgressBar",
226+
help="Show curses based progress bar",
227+
action="store_true",
228+
)
108229
format_group.add_argument(
109230
"--no-progress-bar",
110231
dest="useProgressBar",
111-
help="Do not use curses based progress bar",
232+
help="Do not use curses based progress bar (default)",
112233
action="store_false",
113234
)
114235

0 commit comments

Comments
 (0)