On Thu, 15 Jan 2026 at 22:54, Ryota Sakamoto <[email protected]> wrote: > > Currently, kunit.py has many subcommands and options, making it difficult > to remember them without checking the help message. > > Add --list-cmds and --list-opts to kunit.py to get available commands and > options, use those outputs in kunit-completion.sh to show completion. > > This implementation is similar to perf and tools/perf/perf-completion.sh. > > Example output: > $ source tools/testing/kunit/kunit-completion.sh > $ ./tools/testing/kunit/kunit.py [TAB][TAB] > build config exec parse run > $ ./tools/testing/kunit/kunit.py run --k[TAB][TAB] > --kconfig_add --kernel_args --kunitconfig > > Signed-off-by: Ryota Sakamoto <[email protected]> > ---
This is awesome! Two small suggestions: - Could we add './tools/testing/kunit/kunit.py' to the list of commands? That's what's recommended in lots of documentation, emails, etc. - It'd be great to rebase this on top of kselftest/kunit -- there's a conflict with your previous patch. Otherwise, this is great! Reviewed-by: David Gow <[email protected]> Cheers, -- David > Documentation/dev-tools/kunit/run_wrapper.rst | 9 ++++++++ > tools/testing/kunit/kunit-completion.sh | 33 > +++++++++++++++++++++++++++ > tools/testing/kunit/kunit.py | 30 ++++++++++++++++++++++++ > tools/testing/kunit/kunit_tool_test.py | 21 +++++++++++++++++ > 4 files changed, 93 insertions(+) > > diff --git a/Documentation/dev-tools/kunit/run_wrapper.rst > b/Documentation/dev-tools/kunit/run_wrapper.rst > index > 6697c71ee8ca020b8ac7e91b46e29ab082d9dea0..3c0b585dcfffbd3929d0eef1ab9376fa4f380872 > 100644 > --- a/Documentation/dev-tools/kunit/run_wrapper.rst > +++ b/Documentation/dev-tools/kunit/run_wrapper.rst > @@ -335,3 +335,12 @@ command line arguments: > > - ``--list_tests_attr``: If set, lists all tests that will be run and all of > their > attributes. > + > +Command-line completion > +============================== > + > +The kunit_tool comes with a bash completion script: > + > +.. code-block:: bash > + > + source tools/testing/kunit/kunit-completion.sh > diff --git a/tools/testing/kunit/kunit-completion.sh > b/tools/testing/kunit/kunit-completion.sh > new file mode 100644 > index > 0000000000000000000000000000000000000000..3b9b68e3bc384c026f10f74b8a1df2129cb2cd50 > --- /dev/null > +++ b/tools/testing/kunit/kunit-completion.sh > @@ -0,0 +1,33 @@ > +# SPDX-License-Identifier: GPL-2.0 > +# bash completion support for KUnit > + > +_kunit_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) > + > +_kunit() > +{ > + local cur prev words cword > + _init_completion || return > + > + local script="${_kunit_dir}/kunit.py" > + > + if [[ $cword -eq 1 && "$cur" != -* ]]; then > + local cmds=$(${script} --list-cmds 2>/dev/null) > + COMPREPLY=($(compgen -W "${cmds}" -- "$cur")) > + return 0 > + fi > + > + if [[ "$cur" == -* ]]; then > + if [[ -n "${words[1]}" && "${words[1]}" != -* ]]; then > + local opts=$(${script} ${words[1]} --list-opts > 2>/dev/null) > + COMPREPLY=($(compgen -W "${opts}" -- "$cur")) > + return 0 > + else > + local opts=$(${script} --list-opts 2>/dev/null) > + COMPREPLY=($(compgen -W "${opts}" -- "$cur")) > + return 0 > + fi > + fi > +} > + > +complete -o default -F _kunit kunit.py > +complete -o default -F _kunit kunit Can we add: complete -o default -F _kunit ./tools/testing/kunit/kunit.py as well? > diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py > index > cd99c1956331dbbfb06cf4ddf130db3dcf2a7c31..a5aee1eb88e65fa2387b2623642d2ee9a66db600 > 100755 > --- a/tools/testing/kunit/kunit.py > +++ b/tools/testing/kunit/kunit.py > @@ -323,6 +323,17 @@ def get_default_jobs() -> int: > return ncpu > raise RuntimeError("os.cpu_count() returned None") > > +def add_completion_opts(parser: argparse.ArgumentParser) -> None: > + parser.add_argument('--list-opts', > + help=argparse.SUPPRESS, > + action='store_true') > + > +def add_root_opts(parser: argparse.ArgumentParser) -> None: > + parser.add_argument('--list-cmds', > + help=argparse.SUPPRESS, > + action='store_true') > + add_completion_opts(parser) > + > def add_common_opts(parser: argparse.ArgumentParser) -> None: > parser.add_argument('--build_dir', > help='As in the make command, it specifies the > build ' > @@ -374,6 +385,8 @@ def add_common_opts(parser: argparse.ArgumentParser) -> > None: > help='Additional QEMU arguments, e.g. "-smp 8"', > action='append', metavar='') > > + add_completion_opts(parser) > + > def add_build_opts(parser: argparse.ArgumentParser) -> None: > parser.add_argument('--jobs', > help='As in the make command, "Specifies the > number of ' > @@ -569,6 +582,7 @@ subcommand_handlers_map = { > def main(argv: Sequence[str]) -> None: > parser = argparse.ArgumentParser( > description='Helps writing and running KUnit tests.') > + add_root_opts(parser) > subparser = parser.add_subparsers(dest='subcommand') > > # The 'run' command will config, build, exec, and parse in one go. > @@ -603,12 +617,28 @@ def main(argv: Sequence[str]) -> None: > parse_parser.add_argument('file', > help='Specifies the file to read results > from.', > type=str, nargs='?', metavar='input_file') > + add_completion_opts(parse_parser) > > cli_args = parser.parse_args(massage_argv(argv)) > > if get_kernel_root_path(): > os.chdir(get_kernel_root_path()) > > + if cli_args.list_cmds: > + print(" ".join(subparser.choices.keys())) > + return > + > + if cli_args.list_opts: > + target_parser = subparser.choices.get(cli_args.subcommand) > + if not target_parser: > + target_parser = parser > + > + # Accessing private attribute _option_string_actions to get > + # the list of options. This is not a public API, but argparse > + # does not provide a way to inspect options programmatically. > + print(' '.join(target_parser._option_string_actions.keys())) > + return > + > subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, > None) > > if subcomand_handler is None: > diff --git a/tools/testing/kunit/kunit_tool_test.py > b/tools/testing/kunit/kunit_tool_test.py > index > bbba921e0eacb18663abfcabb2bccf330d8666f5..a7f09a6c97a473ff85e087d17c2f5faf7755b994 > 100755 > --- a/tools/testing/kunit/kunit_tool_test.py > +++ b/tools/testing/kunit/kunit_tool_test.py > @@ -11,11 +11,13 @@ from unittest import mock > > import tempfile, shutil # Handling test_tmpdir > > +import io > import itertools > import json > import os > import signal > import subprocess > +import sys > from typing import Iterable > > import kunit_config > @@ -855,5 +857,24 @@ class KUnitMainTest(unittest.TestCase): > mock.call(args=None, build_dir='.kunit', > filter_glob='suite2.test1', filter='', filter_action=None, timeout=300), > ]) > > + @mock.patch.object(sys, 'stdout', new_callable=io.StringIO) > + def test_list_cmds(self, mock_stdout): > + kunit.main(['--list-cmds']) > + output = mock_stdout.getvalue() > + output_cmds = sorted(output.split()) > + expected_cmds = sorted(['build', 'config', 'exec', 'parse', > 'run']) > + self.assertEqual(output_cmds, expected_cmds) > + > + @mock.patch.object(sys, 'stdout', new_callable=io.StringIO) > + def test_run_list_opts(self, mock_stdout): > + kunit.main(['run', '--list-opts']) > + output = mock_stdout.getvalue() > + output_cmds = set(output.split()) > + self.assertIn('--help', output_cmds) > + self.assertIn('--kunitconfig', output_cmds) > + self.assertIn('--jobs', output_cmds) > + self.assertIn('--kernel_args', output_cmds) > + self.assertIn('--raw_output', output_cmds) > + > if __name__ == '__main__': > unittest.main() > > --- > base-commit: b71e635feefc852405b14620a7fc58c4c80c0f73 > change-id: 20260114-kunit-completion-265889f59c52 > > Best regards, > -- > Ryota Sakamoto <[email protected]> >
smime.p7s
Description: S/MIME Cryptographic Signature
