1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-10-23 21:28:56 +03:00

Allow test.py options before the command.

When a command is given, argparse parses the options of the command into a new namespace and copies every value, including defaults, over the values the main parser set from options before the command. As a result, `test.py --vm=u24 vm-build` ran vm-build with --vm=none.

Move the command to the front of the argument list before parsing so the command parser parses every option. An option before the command that the command does not define is now an error instead of being ignored.
This commit is contained in:
David Steele
2026-09-27 17:42:33 +03:00
parent 36afca4636
commit fe2c883c23
2 changed files with 50 additions and 6 deletions

View File

@@ -13,8 +13,7 @@ the help shows them without being asked for the command.
The unit command is not in the command list the help shows since a test run is what calls it, in a container where only the
repository copy is available, rather than something to run by hand.
A command parses into the same configuration as the test run, so an option belongs after the command, which replaces whatever was
given before it."""
Options may be given before or after the command. The command parser parses all of them."""
####################################################################################################################################
import argparse
@@ -157,6 +156,37 @@ def _parser_vm_build(command, parent):
result.add_argument("--no-cache", dest="cache", action="store_false", help="build the containers without the cache")
####################################################################################################################################
def _command_first(arg_list, parser, command_map):
"""Move the command to the front of the argument list.
The command is the first argument that is not an option or an option value. The argument list is unchanged when there is no
command."""
# Options that take a value in the main parser or any command parser
option_value = set()
for parser_option in [parser, *command_map.values()]:
for action in parser_option._actions:
if action.nargs != 0:
option_value.update(action.option_strings)
index = 0
while index < len(arg_list):
arg = arg_list[index]
if not arg.startswith("-"):
if arg in command_map:
return [arg] + arg_list[:index] + arg_list[index + 1 :]
break
index += 2 if arg in option_value else 1
return arg_list
####################################################################################################################################
def cli_parse(arg_list, version):
"""Build the parser and parse the command line."""
@@ -191,7 +221,8 @@ def cli_parse(arg_list, version):
)
code_format.add_argument("--check", action="store_true", help="check the formatting rather than changing it")
result = parser.parse_args(arg_list)
# The command parser parses every option, including those given before the command
result = parser.parse_args(_command_first(arg_list, parser, command.choices))
# Fill in the command a command line with no command was asking for, so nothing downstream has to know it was left out
if result.command is None:

View File

@@ -160,8 +160,8 @@ def test_cli_test():
assert_true(config.lint_only)
# The command can be named, which is the same run with the same options
config = cli_parse(["test", "--vm=u24", "--module=common/error"], VERSION)
# The command can be named, which is the same run with the same options before or after it
config = cli_parse(["--vm", "u24", "test", "--module=common/error"], VERSION)
assert_equal(config.command, "test")
assert_equal(config.vm, "u24")
@@ -181,7 +181,8 @@ def test_cli_vm_build():
assert_true(config.cache)
assert_is_none(config.vm_arch)
config = cli_parse(["vm-build", "--vm=all", "--vm-arch=aarch64", "--no-cache"], VERSION)
# Options before the command
config = cli_parse(["--vm=all", "--vm-arch", "aarch64", "--no-cache", "vm-build"], VERSION)
assert_equal(config.vm, "all")
assert_equal(config.vm_arch, "aarch64")
@@ -201,6 +202,18 @@ def test_cli_error():
assert_equal(status, 2)
assert_in("unrecognized arguments: --bogus", output)
# An option before the command that the command does not define
status, output = _cli_parse_exit(["--module=common/error", "vm-build"])
assert_equal(status, 2)
assert_in("unrecognized arguments: --module=common/error", output)
# An argument after the options that is not a command
status, output = _cli_parse_exit(["--vm=u24", "bogus"])
assert_equal(status, 2)
assert_in("invalid choice: 'bogus'", output)
# A level outside the log levels is rejected here rather than later when it is converted
status, output = _cli_parse_exit(["unit", "common/error", "--log-level=bogus"])