Skip to content

Commit

Permalink
Fix passthrough argument parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsmith committed Feb 6, 2023
1 parent 8c48c6e commit 32f854d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 56 deletions.
21 changes: 11 additions & 10 deletions src/briefcase/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
UpdateCommand,
UpgradeCommand,
)
from briefcase.commands.base import split_passthrough
from briefcase.platforms import get_output_formats, get_platforms

from .exceptions import InvalidFormatError, NoCommandError, UnsupportedCommandError
Expand Down Expand Up @@ -102,19 +103,19 @@ def parse_cmdline(args):
def normalize(name):
return {n.lower(): n for n in platforms.keys()}.get(name.lower(), name)

# argparse handles `--` specially, so make the passthrough args bypass the parser.
def parse_known_args(args):
args, passthough = split_passthrough(args)
options, extra = parser.parse_known_args(args)
if passthough:
extra += ["--"] + passthough
return options, extra

# Use parse_known_args to ensure any extra arguments can be ignored,
# and parsed as part of subcommand handling. This will capture the
# command, platform (filling a default if unspecified) and format
# (with no value if unspecified).
options, extra = parser.parse_known_args(args)

# parse_known_args() strips `--` if it is the first unknown argument.
# Put it back in so that passthrough handling is consistent.
try:
if args[1] == "--":
extra.insert(0, "--")
except IndexError:
pass
options, extra = parse_known_args(args)

# If no command has been provided, display top-level help.
if options.command is None:
Expand Down Expand Up @@ -156,7 +157,7 @@ def normalize(name):

# Re-parse the arguments, now that we know it is a command that makes use
# of platform/output_format.
options, extra = parser.parse_known_args(args)
options, extra = parse_known_args(args)

# Import the platform module
platform_module = platforms[options.platform]
Expand Down
23 changes: 12 additions & 11 deletions src/briefcase/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ def full_options(state, options):
return full


def split_passthrough(args):
try:
pos = args.index("--")
except ValueError:
return args, []
else:
return args[:pos], args[pos + 1 :]


class BaseCommand(ABC):
cmd_line = "briefcase {command} {platform} {output_format}"
supported_host_os = {"Darwin", "Linux", "Windows"}
Expand Down Expand Up @@ -504,25 +513,17 @@ def parse_options(self, extra):
self.add_default_options(parser)
self.add_options(parser)

# If the command allows passthrough arguments, add the option,
# If the command allows passthrough arguments, add an option for the help,
# then process the argument list to strip out the passthrough args.
if self.allows_passthrough:
parser.add_argument(
"--",
dest="passthrough",
metavar="passthrough arguments",
metavar="ARGS ...",
required=False,
help="Arguments to pass to the app",
)

# If "--" is present in the argument list, strip off any
# arguments after that one, and store them for separate handling.
if "--" in extra:
pos = extra.index("--")
args, passthrough = extra[:pos], extra[pos + 1 :]
else:
passthrough = []
args = extra
args, passthrough = split_passthrough(extra)
else:
args = extra

Expand Down
96 changes: 61 additions & 35 deletions tests/test_cmdline.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import shlex
import sys
from unittest import mock

Expand All @@ -12,7 +13,11 @@
UnsupportedCommandError,
)
from briefcase.platforms.linux.appimage import LinuxAppImageCreateCommand
from briefcase.platforms.macOS.app import macOSAppCreateCommand, macOSAppPublishCommand
from briefcase.platforms.macOS.app import (
macOSAppCreateCommand,
macOSAppPublishCommand,
macOSAppRunCommand,
)
from briefcase.platforms.windows.app import WindowsAppCreateCommand


Expand Down Expand Up @@ -102,12 +107,44 @@ def test_new_command(logger, console):
assert options == {"template": None}


def test_dev_command(monkeypatch, logger, console):
# Common tests for dev and run commands.
def dev_run_parameters(command):
return [
(f"{command} {args}", expected)
for args, expected in [
("", {}),
("-r", {"update_requirements": True}),
("--update-requirements", {"update_requirements": True}),
("--test", {"test_mode": True}),
("--test -r", {"test_mode": True, "update_requirements": True}),
("--", {}),
("-- ''", {"passthrough": [""]}),
("-- --test", {"passthrough": ["--test"]}),
("--test -- --test", {"test_mode": True, "passthrough": ["--test"]}),
("--test -- -r", {"test_mode": True, "passthrough": ["-r"]}),
("-r -- --test", {"update_requirements": True, "passthrough": ["--test"]}),
("-- -y --no maybe", {"passthrough": ["-y", "--no", "maybe"]}),
(
"--test -- -y --no maybe",
{"test_mode": True, "passthrough": ["-y", "--no", "maybe"]},
),
]
]


@pytest.mark.parametrize(
"cmdline, expected_options",
dev_run_parameters("dev")
+ [
("dev --no-run", {"run_app": False}),
],
)
def test_dev_command(monkeypatch, logger, console, cmdline, expected_options):
"""``briefcase dev`` returns the Dev command."""
# Pretend we're on macOS, regardless of where the tests run.
monkeypatch.setattr(sys, "platform", "darwin")

cmd, options = do_cmdline_parse("dev".split(), logger, console)
cmd, options = do_cmdline_parse(shlex.split(cmdline), logger, console)

assert isinstance(cmd, DevCommand)
assert cmd.platform == "macOS"
Expand All @@ -122,54 +159,43 @@ def test_dev_command(monkeypatch, logger, console):
"run_app": True,
"test_mode": False,
"passthrough": [],
**expected_options,
}


def test_dev_command_only_passthrough(monkeypatch, logger, console):
"""``briefcase dev`` with only passthrough args returns the Dev command."""
@pytest.mark.parametrize(
"cmdline, expected_options",
dev_run_parameters("run")
+ [
("run -u", {"update": True}),
("run --update", {"update": True}),
("run --update-resources", {"update_resources": True}),
("run --no-update", {"no_update": True}),
],
)
def test_run_command(monkeypatch, logger, console, cmdline, expected_options):
"""``briefcase run`` returns the Run command for the correct platform."""
# Pretend we're on macOS, regardless of where the tests run.
monkeypatch.setattr(sys, "platform", "darwin")

cmd, options = do_cmdline_parse("dev -- -y --no maybe".split(), logger, console)
cmd, options = do_cmdline_parse(shlex.split(cmdline), logger, console)

assert isinstance(cmd, DevCommand)
assert isinstance(cmd, macOSAppRunCommand)
assert cmd.platform == "macOS"
assert cmd.output_format is None
assert cmd.output_format == "app"
assert cmd.input.enabled
assert cmd.logger.verbosity == 1
assert cmd.logger is logger
assert cmd.input is console
assert options == {
"appname": None,
"update": False,
"update_requirements": False,
"run_app": True,
"update_resources": False,
"no_update": False,
"test_mode": False,
"passthrough": ["-y", "--no", "maybe"],
}


def test_dev_command_passthrough(monkeypatch, logger, console):
"""``briefcase dev`` with passthrough args returns the Dev command."""
# Pretend we're on macOS, regardless of where the tests run.
monkeypatch.setattr(sys, "platform", "darwin")

cmd, options = do_cmdline_parse(
"dev --test -- -y --no maybe".split(), logger, console
)

assert isinstance(cmd, DevCommand)
assert cmd.platform == "macOS"
assert cmd.output_format is None
assert cmd.input.enabled
assert cmd.logger.verbosity == 1
assert cmd.logger is logger
assert cmd.input is console
assert options == {
"appname": None,
"update_requirements": False,
"run_app": True,
"test_mode": True,
"passthrough": ["-y", "--no", "maybe"],
"passthrough": [],
**expected_options,
}


Expand Down

0 comments on commit 32f854d

Please sign in to comment.