Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/12824.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Custom command-line options now fail with a clear error when their implicit
destination conflicts with an existing option, avoiding silent overrides of
built-in options such as ``-k``.
18 changes: 18 additions & 0 deletions src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
FILE_OR_DIR = "file_or_dir"


def _get_argparse_dest(opts: Sequence[str]) -> str:
long_opts = [opt for opt in opts if opt.startswith("--")]
opt = long_opts[0] if long_opts else opts[0]
return opt.lstrip("-").replace("-", "_")


@final
class Parser:
"""Parser for command line arguments and config-file values.
Expand Down Expand Up @@ -355,6 +361,18 @@ def addoption(self, *opts: str, **attrs: Any) -> None:
)
if conflict:
raise ValueError(f"option names {conflict} already added")

if self.parser and "dest" not in attrs:
dest = _get_argparse_dest(opts)
for group in self.parser._groups:
for option in group.options:
if option.dest == dest:
raise ValueError(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit pick but I am thinking if we make the error message a bit more clear?

Maybe something like:

"option dest keyword already used by ['-k'] (this is the option that maps to dest keyword); pass dest='keyword' explicitly to share the destination"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated the error message to include the existing option and explicit dest guidance.

f"option dest {dest!r} already used by "
f"{option.names()!r} (this is the option that maps to "
f"dest {dest!r}); pass dest={dest!r} explicitly "
"to share the destination"
)
self._addoption_inner(opts, attrs, allow_reserved=False)

def _addoption(self, *opts: str, **attrs: Any) -> None:
Expand Down
31 changes: 31 additions & 0 deletions testing/test_parseopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ def test_argument_type(self) -> None:
argument = parseopt.Argument(action)
assert argument.type is str

def test_get_argparse_dest(self) -> None:
assert parseopt._get_argparse_dest(("--keyword",)) == "keyword"
assert parseopt._get_argparse_dest(("-x",)) == "x"
assert parseopt._get_argparse_dest(("-x", "--exit-first")) == "exit_first"

def test_group_add_and_get(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("hello")
assert group.name == "hello"
Expand Down Expand Up @@ -123,6 +128,32 @@ def test_parser_addoption(self, parser: parseopt.Parser) -> None:
group.addoption("--option1", action="store_true")
assert len(group.options) == 1

def test_group_addoption_rejects_implicit_dest_conflict(
self, parser: parseopt.Parser
) -> None:
group = parser.getgroup("hello")
group._addoption("-k", dest="keyword", action="store")

with pytest.raises(ValueError) as err:
group.addoption("--keyword", action="store")

assert str(err.value) == (
"option dest 'keyword' already used by ['-k'] "
"(this is the option that maps to dest 'keyword'); "
"pass dest='keyword' explicitly to share the destination"
)

def test_group_addoption_allows_explicit_dest_conflict(
self, parser: parseopt.Parser
) -> None:
group = parser.getgroup("hello")
group.addoption("--capture", action="store", default="fd")
group._addoption("-s", dest="capture", action="store_const", const="no")

args = parser.parse(["-s"])

assert args.capture == "no"

def test_parse(self, parser: parseopt.Parser) -> None:
parser.addoption("--hello", dest="hello", action="store")
args = parser.parse(["--hello", "world"])
Expand Down
Loading