From nobody Sun Feb 8 04:11:44 2026 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EEE6178F26 for ; Fri, 16 Jan 2026 17:46:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768585622; cv=none; b=YCeu+rXixtcIMC9fnYRtVwNFvH0vz+G8fyLQ6MNXPu+R5PGWD5XXUKeylsALyk0pFz0ZxynM2/YG3U2LmyHQvNkgtzypAwHW6d7Vvc823XolBlSiCe+nIgKn/Cm7DogvFs7N/qSNmPsJ1/7rfoasRvoADA2YSwJyVBhSARMDwgQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768585622; c=relaxed/simple; bh=S3CI7rQIWs9HJdUvqhIwtwa5rLnoqW9fYA/1FX4zfVU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:To:Cc; b=Uy4xJJDHCW9Ec2ZL3LDknwN4nuLrIGYzlavrJzGQAZ2Yfz50CZlPTaXD6xj49WGHXDTGGPMaXESM3QHerQQmvzpkirn9Mp96budbRchMwXhQpqaophlkAnNSgs6DBvMNmWvoFGEOfjgjKiO/8Yt7jBXVM+JSAb4JMgoYYeMBo7w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=NP+GWIyA; arc=none smtp.client-ip=209.85.214.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="NP+GWIyA" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2a081c163b0so13398995ad.0 for ; Fri, 16 Jan 2026 09:46:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1768585619; x=1769190419; darn=vger.kernel.org; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:from:to:cc:subject:date:message-id:reply-to; bh=bqprM+/fMABEF2BT0yjlCPRYQsErnl1lQWu3bm8SrTk=; b=NP+GWIyAZLJui2f3BwvN4yRvYZwfqEG7RZu1EXzvuTFO677zumYUcvZZhN57CyrtrD bqVFLtAXXx67z0q7dUn43JdiEBimy90kRnNjD0ZS/YR3f+V7qyPFBskeCR15ikY74qpT 9qC+fqCflczSvbJm+J0QdMD8sLh7kZntVuCQC+nQjYAmhan/CJaKrw/VZd6V2IERU0of fe4sTUymMeQT4LIuCtCVxF9RAHlHdGtdpR9xBvl38ncke/3PDg6FD8SJUg92gIeUfKIA oNQ/PU7kFSOO/1rmeyP7NIOY0NLauaCy+51sVmbXmxhvIh5qElOQgFt1e0HK2wz2S20d 44AA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768585619; x=1769190419; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=bqprM+/fMABEF2BT0yjlCPRYQsErnl1lQWu3bm8SrTk=; b=MYR9kvtrvDNRelQYroRW7qwWB3NUa7z6EOf9YCac887+VLqgEGNaqTEK6o2GkQbf4n jn2UvYU6ZVmpY41Tehyl3crH1trZv4ZoRAq+05S/oLV7IhdbPEZdRlMijjZ3pR4qgcdO vPoyBjpBnbl04WGPc0so2+oLGUvRhBCChnfg8viSNx7Sy8ZDFTe5F94wJCp68FDuJKOr kR8A3i8uqE6Do+/uy/4xvpUrIo3FHqErrS+imdd/z/YILH7JNU/Ke12o3VSYMUI9kGye R+7214YNwOjsDm/kGVkR2ywz1x42JZtBbMp+/guhd2nE8/ZGKDq5sit3zzIt3zg5RMEd xGaw== X-Forwarded-Encrypted: i=1; AJvYcCVJpcbUyz31/60Uypvs5huCCmjf4JA+2ApUJjrCKFj0E1AkKDL+BIwW4GC6OCHcwPsCMh/G+NYd4RKzZGA=@vger.kernel.org X-Gm-Message-State: AOJu0YyCNBc+jxBqUn4e9sm19laRyOs3UJ+K27GspEy0z3f4BiTmscIg JdpRMvWU0PZ3OtUPKjHpnfmvsvx/iaqwdfamaz1S6mTlv4G86bq6SHFF X-Gm-Gg: AY/fxX41AxenGjJVZOw5mtYiToEXxBL8I3VAMoUGL9ZI/bIQqrpAF8sR6yj2Dfa4/M2 FHR+5Pe4p4lcKlhrDP+YwisjFldTe/3vFjsDDfp7PucjhxGDHochtjBJQqUtuh8Z35tovi6qsWV l/mjhG8AFSgjnaq+nWzbqLGlZd6KpXOCeVMtcGzkIy3UPxRaD/02QKhlNsfj55T3yG5R/eziiZ1 RKuC879Y5PZKHhkFMG6tc/0u9Gs29hY9lNSk9TRg9P6aZL16KJTYiPI4K1dBiOpxGNQdqE2YkvH JAUL3nLMmCKPEjrMyjenn6cBzJZOX53aVACVMM6crZs5mUZ2i13WhGnu9To6QN1yi4jqlCANYez t7WQaXQ6lgCHDuD8lOngBxdP7lu1Xw30+obDc0Y+YxOAhs5TmTUOdljaxCZ5elHyj6J8CFtqJ7v alvva0uDqpzOQz2S3D/eA= X-Received: by 2002:a17:903:2f82:b0:2a0:992c:c54e with SMTP id d9443c01a7336-2a7177daea5mr34315265ad.48.1768585618643; Fri, 16 Jan 2026 09:46:58 -0800 (PST) Received: from [172.16.80.107] ([210.228.119.9]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2a7193decfcsm26573895ad.60.2026.01.16.09.46.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 16 Jan 2026 09:46:58 -0800 (PST) From: Ryota Sakamoto Date: Sat, 17 Jan 2026 02:46:34 +0900 Subject: [PATCH v2] kunit: add bash completion Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260117-kunit-completion-v2-1-cabd127d0801@gmail.com> X-B4-Tracking: v=1; b=H4sIAAAAAAAC/2WNQQ6DIBREr2L+ujRCgEhXvYdxYfCrP1U0QEkbw 91LTbrq8k1m3hwQ0BMGuFUHeEwUaHMFxKUCO/duQkZDYRC10DXnkj2ejiKz27ovGEuZCa2axoz KWCWgzHaPI71OZdsVninEzb/Ph8S/6U+m/mWJM87kgFppabSw8j6tPS3XUoEu5/wBv+q0ZLAAA AA= X-Change-ID: 20260114-kunit-completion-265889f59c52 To: Brendan Higgins , David Gow , Rae Moar , Jonathan Corbet Cc: Jani Nikula , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, kunit-dev@googlegroups.com, workflows@vger.kernel.org, linux-doc@vger.kernel.org, Ryota Sakamoto X-Mailer: b4 0.14.2 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 Reviewed-by: David Gow Signed-off-by: Ryota Sakamoto --- Changes in v2: - Add relative path './tools/testing/kunit/kunit.py' to completion list - Rebase on kselftest/kunit branch - Link to v1: https://lore.kernel.org/r/20260115-kunit-completion-v1-1-4de6= 564962c4@gmail.com --- Documentation/dev-tools/kunit/run_wrapper.rst | 9 +++++++ tools/testing/kunit/kunit-completion.sh | 34 +++++++++++++++++++++++= ++++ tools/testing/kunit/kunit.py | 30 +++++++++++++++++++++++ tools/testing/kunit/kunit_tool_test.py | 21 +++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/Documentation/dev-tools/kunit/run_wrapper.rst b/Documentation/= dev-tools/kunit/run_wrapper.rst index 6697c71ee8ca020b8ac7e91b46e29ab082d9dea0..3c0b585dcfffbd3929d0eef1ab9= 376fa4f380872 100644 --- a/Documentation/dev-tools/kunit/run_wrapper.rst +++ b/Documentation/dev-tools/kunit/run_wrapper.rst @@ -335,3 +335,12 @@ command line arguments: =20 - ``--list_tests_attr``: If set, lists all tests that will be run and all = of their attributes. + +Command-line completion +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +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..f053e7b5d265aec8317b6eb11c8= 920063607073f --- /dev/null +++ b/tools/testing/kunit/kunit-completion.sh @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 +# bash completion support for KUnit + +_kunit_dir=3D$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +_kunit() +{ + local cur prev words cword + _init_completion || return + + local script=3D"${_kunit_dir}/kunit.py" + + if [[ $cword -eq 1 && "$cur" !=3D -* ]]; then + local cmds=3D$(${script} --list-cmds 2>/dev/null) + COMPREPLY=3D($(compgen -W "${cmds}" -- "$cur")) + return 0 + fi + + if [[ "$cur" =3D=3D -* ]]; then + if [[ -n "${words[1]}" && "${words[1]}" !=3D -* ]]; then + local opts=3D$(${script} ${words[1]} --list-opts 2>/dev/null) + COMPREPLY=3D($(compgen -W "${opts}" -- "$cur")) + return 0 + else + local opts=3D$(${script} --list-opts 2>/dev/null) + COMPREPLY=3D($(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 diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index e3d82a038f93df0e86952da92461bc2e02f69ed1..4ec5ecba6d49b1ba3360515a2b6= 6a2a98813bd18 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -328,6 +328,17 @@ def get_default_build_dir() -> str: return os.path.join(os.environ['KBUILD_OUTPUT'], '.kunit') return '.kunit' =20 +def add_completion_opts(parser: argparse.ArgumentParser) -> None: + parser.add_argument('--list-opts', + help=3Dargparse.SUPPRESS, + action=3D'store_true') + +def add_root_opts(parser: argparse.ArgumentParser) -> None: + parser.add_argument('--list-cmds', + help=3Dargparse.SUPPRESS, + action=3D'store_true') + add_completion_opts(parser) + def add_common_opts(parser: argparse.ArgumentParser) -> None: parser.add_argument('--build_dir', help=3D'As in the make command, it specifies the build ' @@ -379,6 +390,8 @@ def add_common_opts(parser: argparse.ArgumentParser) ->= None: help=3D'Additional QEMU arguments, e.g. "-smp 8"', action=3D'append', metavar=3D'') =20 + add_completion_opts(parser) + def add_build_opts(parser: argparse.ArgumentParser) -> None: parser.add_argument('--jobs', help=3D'As in the make command, "Specifies the number of ' @@ -574,6 +587,7 @@ subcommand_handlers_map =3D { def main(argv: Sequence[str]) -> None: parser =3D argparse.ArgumentParser( description=3D'Helps writing and running KUnit tests.') + add_root_opts(parser) subparser =3D parser.add_subparsers(dest=3D'subcommand') =20 # 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=3D'Specifies the file to read results from.', type=3Dstr, nargs=3D'?', metavar=3D'input_file') + add_completion_opts(parse_parser) =20 cli_args =3D parser.parse_args(massage_argv(argv)) =20 if get_kernel_root_path(): os.chdir(get_kernel_root_path()) =20 + if cli_args.list_cmds: + print(" ".join(subparser.choices.keys())) + return + + if cli_args.list_opts: + target_parser =3D subparser.choices.get(cli_args.subcommand) + if not target_parser: + target_parser =3D 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 =3D subcommand_handlers_map.get(cli_args.subcommand, No= ne) =20 if subcomand_handler is None: diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/k= unit_tool_test.py index 238a31a5cc291854bb8738f22e04c65bcbaeb11c..b67408147c1faaab12b168aabe3= bfba8bf1b00aa 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 =20 import tempfile, shutil # Handling test_tmpdir =20 +import io import itertools import json import os import signal import subprocess +import sys from typing import Iterable =20 import kunit_config @@ -886,5 +888,24 @@ class KUnitMainTest(unittest.TestCase): mock.call(args=3DNone, build_dir=3D'.kunit', filter_glob=3D'suite2.test= 1', filter=3D'', filter_action=3DNone, timeout=3D300), ]) =20 + @mock.patch.object(sys, 'stdout', new_callable=3Dio.StringIO) + def test_list_cmds(self, mock_stdout): + kunit.main(['--list-cmds']) + output =3D mock_stdout.getvalue() + output_cmds =3D sorted(output.split()) + expected_cmds =3D sorted(['build', 'config', 'exec', 'parse', 'run']) + self.assertEqual(output_cmds, expected_cmds) + + @mock.patch.object(sys, 'stdout', new_callable=3Dio.StringIO) + def test_run_list_opts(self, mock_stdout): + kunit.main(['run', '--list-opts']) + output =3D mock_stdout.getvalue() + output_cmds =3D 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__ =3D=3D '__main__': unittest.main() --- base-commit: f126d688193b4dd6d0044c19771469724c03f8f8 change-id: 20260114-kunit-completion-265889f59c52 Best regards, --=20 Ryota Sakamoto