kunit: add bash completion

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

Link: https://lore.kernel.org/r/20260117-kunit-completion-v2-1-cabd127d0801@gmail.com
Reviewed-by: David Gow <davidgow@google.com>
Signed-off-by: Ryota Sakamoto <sakamo.ryota@gmail.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
This commit is contained in:
Ryota Sakamoto 2026-01-17 02:46:34 +09:00 committed by Shuah Khan
parent f126d68819
commit db0c35ca36
4 changed files with 94 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,34 @@
# 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
complete -o default -F _kunit ./tools/testing/kunit/kunit.py

View file

@ -328,6 +328,17 @@ def get_default_build_dir() -> str:
return os.path.join(os.environ['KBUILD_OUTPUT'], '.kunit')
return '.kunit'
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 '
@ -379,6 +390,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 '
@ -574,6 +587,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.
@ -608,12 +622,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:

View file

@ -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
@ -886,5 +888,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()