Skip to content

Commit 2404902

Browse files
committed
[utils] revamp options controlling lit's output
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 --test-progress=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 3bb903e commit 2404902

File tree

21 files changed

+1335
-52
lines changed

21 files changed

+1335
-52
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,12 @@ def push_dynamic_library_lookup_path(config, new_path):
196196
if test_cc_resource_dir is not None:
197197
test_cc_resource_dir = os.path.realpath(test_cc_resource_dir)
198198
if lit_config.debug:
199-
lit_config.note(f"Resource dir for {config.clang} is {test_cc_resource_dir}")
199+
lit_config.dbg(f"Resource dir for {config.clang} is {test_cc_resource_dir}")
200200
local_build_resource_dir = os.path.realpath(config.compiler_rt_output_dir)
201201
if test_cc_resource_dir != local_build_resource_dir and config.test_standalone_build_libs:
202202
if config.compiler_id == "Clang":
203203
if lit_config.debug:
204-
lit_config.note(
204+
lit_config.dbg(
205205
f"Overriding test compiler resource dir to use "
206206
f'libraries in "{config.compiler_rt_libdir}"'
207207
)

libcxx/utils/libcxx/test/config.py

Lines changed: 3 additions & 2 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
@@ -32,7 +33,7 @@ def configure(parameters, features, config, lit_config):
3233
for action in actions:
3334
action.applyTo(config)
3435
if lit_config.debug:
35-
note(
36+
debug(
3637
"Applied '{}' as a result of parameter '{}'".format(
3738
action.pretty(config, lit_config.params),
3839
param.pretty(config, lit_config.params),
@@ -45,7 +46,7 @@ def configure(parameters, features, config, lit_config):
4546
for action in actions:
4647
action.applyTo(config)
4748
if lit_config.debug:
48-
note(
49+
debug(
4950
"Applied '{}' as a result of implicitly detected feature '{}'".format(
5051
action.pretty(config, lit_config.params), feature.pretty(config)
5152
)

llvm/utils/lit/lit/LitConfig.py

Lines changed: 35 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,19 @@ 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(kind)
243+
244+
def dbg(self, message):
245+
self._write_message("debug", message)
246+
237247
def note(self, message):
238-
if not self.quiet:
239-
self._write_message("note", message)
248+
self._write_message("note", message)
240249

241250
def warning(self, message):
242-
if not self.quiet:
243-
self._write_message("warning", message)
251+
self._write_message("warning", message)
244252
self.numWarnings += 1
245253

246254
def error(self, message):
@@ -250,3 +258,22 @@ def error(self, message):
250258
def fatal(self, message):
251259
self._write_message("fatal", message)
252260
sys.exit(2)
261+
262+
@enum.unique
263+
class DiagnosticLevel(enum.IntEnum):
264+
FATAL = 0
265+
ERROR = 1
266+
WARNING = 2
267+
NOTE = 3
268+
269+
@classmethod
270+
def create(cls, value):
271+
if value == "fatal":
272+
return cls.FATAL
273+
if value == "error":
274+
return cls.ERROR
275+
if value == "warning":
276+
return cls.WARNING
277+
if value == "note":
278+
return cls.NOTE
279+
raise ValueError(f"invalid diagnostic level {repr(value)} of type {type(value)}")

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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def load_from_path(self, path, litConfig):
144144
try:
145145
exec(compile(data, path, "exec"), cfg_globals, None)
146146
if litConfig.debug:
147-
litConfig.note("... loaded config %r" % path)
147+
litConfig.dbg("... loaded config %r" % path)
148148
except SystemExit:
149149
e = sys.exc_info()[1]
150150
# We allow normal system exit inside a config file to just

llvm/utils/lit/lit/cl_arguments.py

Lines changed: 121 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,58 @@ 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+
#print(dest, value)
46+
if dest == "test_output" and TestOutputLevel.create(namespace.print_result_after) < TestOutputLevel.create(value):
47+
#print("print_result_after", value)
48+
setattr(namespace, "print_result_after", value)
49+
elif dest == "print_result_after" and TestOutputLevel.create(namespace.test_output) > TestOutputLevel.create(value):
50+
#print("test_output", value)
51+
setattr(namespace, "test_output", value)
52+
53+
54+
class AliasAction(argparse.Action):
55+
def __init__(self, option_strings, dest, nargs=None, **kwargs):
56+
self.expansion = kwargs.pop("alias", None)
57+
if not self.expansion:
58+
raise ValueError("no aliases expansion provided")
59+
super().__init__(option_strings, dest, nargs=0, **kwargs)
60+
61+
def __call__(self, parser, namespace, value, option_string=None):
62+
for e in self.expansion:
63+
if callable(e):
64+
e(namespace)
65+
else:
66+
dest, val = e
67+
setattr(namespace, dest, val)
68+
69+
1870
def parse_args():
1971
parser = argparse.ArgumentParser(prog="lit", fromfile_prefix_chars="@")
2072
parser.add_argument(
@@ -55,41 +107,86 @@ def parse_args():
55107
)
56108

57109
format_group = parser.add_argument_group("Output Format")
58-
# FIXME: I find these names very confusing, although I like the
59-
# functionality.
60110
format_group.add_argument(
61-
"-q", "--quiet", help="Suppress no error output", action="store_true"
111+
"--test-output",
112+
help="Control whether the executed commands and their outputs are printed after each test has executed (default off)",
113+
choices=["off", "failed", "all"],
114+
default="off",
115+
action=TestOutputAction,
116+
)
117+
format_group.add_argument(
118+
"--print-result-after",
119+
help="Control which the executed test names and results are printed after each test has executed (default all)",
120+
choices=["off", "failed", "all"],
121+
default="all",
122+
action=TestOutputAction,
123+
)
124+
format_group.add_argument(
125+
"--diagnostic-level",
126+
help="Control how verbose lit diagnostics should be (default note)",
127+
choices=["error", "warning", "note"],
128+
default="note",
129+
)
130+
format_group.add_argument(
131+
"--terse-summary",
132+
help="Print the elapsed time and the number of passed tests after all tests have finished (default on)",
133+
action="store_true",
134+
dest="terse_summary",
135+
)
136+
format_group.add_argument(
137+
"--no-terse-summary",
138+
help="Don't show the elapsed time after all tests have finished, and only show the number of failed tests.",
139+
action="store_false",
140+
dest="terse_summary",
141+
)
142+
parser.set_defaults(terse_summary=False)
143+
format_group.add_argument(
144+
"-q", "--quiet", help="Alias for '--diagnostic-level=error --test-output=off --terse-summary'", action=AliasAction,
145+
alias=[
146+
lambda namespace: TestOutputAction.setOutputLevel(namespace, "print_result_after", "failed"),
147+
lambda namespace: TestOutputAction.setOutputLevel(namespace, "test_output", "off"),
148+
("diagnostic_level", "error"),
149+
("terse_summary", True),
150+
],
62151
)
63152
format_group.add_argument(
64153
"-s",
65154
"--succinct",
66-
help="Reduce amount of output."
67-
" Additionally, show a progress bar,"
68-
" unless --no-progress-bar is specified.",
69-
action="store_true",
155+
help="Alias for '--progress-bar --print-result-after=failed'",
156+
action=AliasAction,
157+
alias=[
158+
("useProgressBar", True),
159+
lambda namespace: TestOutputAction.setOutputLevel(namespace, "print_result_after", "failed"),
160+
],
70161
)
71162
format_group.add_argument(
72163
"-v",
73164
"--verbose",
74-
dest="showOutput",
75165
help="For failed tests, show all output. For example, each command is"
76166
" printed before it is executed, so the last printed command is the one"
77-
" that failed.",
78-
action="store_true",
167+
" that failed. Alias for '--test-output=failed'",
168+
action=AliasAction,
169+
alias=[
170+
lambda namespace: TestOutputAction.setOutputLevel(namespace, "test_output", "failed"),
171+
],
79172
)
80173
format_group.add_argument(
81174
"-vv",
82175
"--echo-all-commands",
83-
dest="showOutput",
84176
help="Deprecated alias for -v.",
85-
action="store_true",
177+
action=AliasAction,
178+
alias=[
179+
lambda namespace: TestOutputAction.setOutputLevel(namespace, "test_output", "all"),
180+
],
86181
)
87182
format_group.add_argument(
88183
"-a",
89184
"--show-all",
90-
dest="showAllOutput",
91-
help="Enable -v, but for all tests not just failed tests.",
92-
action="store_true",
185+
help="Enable -v, but for all tests not just failed tests. Alias for '--test-output=all'",
186+
action=AliasAction,
187+
alias=[
188+
lambda namespace: TestOutputAction.setOutputLevel(namespace, "test_output", "all"),
189+
],
93190
)
94191
format_group.add_argument(
95192
"-r",
@@ -105,10 +202,16 @@ def parse_args():
105202
help="Write test results to the provided path",
106203
metavar="PATH",
107204
)
205+
format_group.add_argument(
206+
"--progress-bar",
207+
dest="useProgressBar",
208+
help="Show curses based progress bar",
209+
action="store_true",
210+
)
108211
format_group.add_argument(
109212
"--no-progress-bar",
110213
dest="useProgressBar",
111-
help="Do not use curses based progress bar",
214+
help="Do not use curses based progress bar (default)",
112215
action="store_false",
113216
)
114217

@@ -395,6 +498,8 @@ def parse_args():
395498

396499
for report in opts.reports:
397500
report.use_unique_output_file_name = opts.use_unique_output_file_name
501+
#print("test_output", opts.test_output)
502+
#print("print_result_after", opts.print_result_after)
398503

399504
return opts
400505

llvm/utils/lit/lit/discovery.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def search1(path):
6363

6464
# We found a test suite, create a new config for it and load it.
6565
if litConfig.debug:
66-
litConfig.note("loading suite config %r" % cfgpath)
66+
litConfig.dbg("loading suite config %r" % cfgpath)
6767

6868
cfg = TestingConfig.fromdefaults(litConfig)
6969
cfg.load_from_path(cfgpath, litConfig)
@@ -116,7 +116,7 @@ def search1(path_in_suite):
116116
# file into it.
117117
config = copy.deepcopy(parent)
118118
if litConfig.debug:
119-
litConfig.note("loading local config %r" % cfgpath)
119+
litConfig.dbg("loading local config %r" % cfgpath)
120120
config.load_from_path(cfgpath, litConfig)
121121
return config
122122

@@ -138,7 +138,7 @@ def getTests(path, litConfig, testSuiteCache, localConfigCache):
138138
return (), ()
139139

140140
if litConfig.debug:
141-
litConfig.note("resolved input %r to %r::%r" % (path, ts.name, path_in_suite))
141+
litConfig.dbg("resolved input %r to %r::%r" % (path, ts.name, path_in_suite))
142142

143143
return ts, getTestsInSuite(
144144
ts,

0 commit comments

Comments
 (0)