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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ Sylvain Marié
Tadek Teleżyński
Takafumi Arakaki
Takumi Otani
Tammy Hartline
Taneli Hukkinen
Tanvi Mehta
Tanya Agarwal
Expand Down
1 change: 1 addition & 0 deletions changelog/13817.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``AttributeError`` in ``Argument.__repr__`` when ``dest`` attribute is not set, which occurred when displaying error messages for invalid option names. The error now shows a helpful message instead of masking the original validation error.
2 changes: 1 addition & 1 deletion src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ def __repr__(self) -> str:
args += ["_short_opts: " + repr(self._short_opts)]
if self._long_opts:
args += ["_long_opts: " + repr(self._long_opts)]
args += ["dest: " + repr(self.dest)]
args += ["dest: " + repr(getattr(self, "dest", NOT_SET))]
if hasattr(self, "type"):
args += ["type: " + repr(self.type)]
if hasattr(self, "default"):
Expand Down
79 changes: 79 additions & 0 deletions testing/test_argparsing_repr_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Test case for issue #13817: AttributeError with invalid flag in pytest_addoption."""

from __future__ import annotations

from _pytest.config.argparsing import ArgumentError
from _pytest.config.argparsing import Parser
import pytest


# Suppress warning about using private pytest API (we're testing pytest itself)
@pytest.mark.filterwarnings("ignore::pytest.PytestDeprecationWarning")
class TestArgumentReprFix:
"""Test that Argument.__repr__ handles missing dest attribute."""

def test_invalid_option_without_dashes(self) -> None:
"""Test that invalid option names produce helpful error messages."""
parser = Parser()

with pytest.raises(ArgumentError) as exc_info:
parser.addoption("shuffle") # Missing required -- prefix

error_message = str(exc_info.value)
assert "invalid long option string" in error_message
assert "shuffle" in error_message
assert "must start with --" in error_message

# Ensure no AttributeError is mentioned
assert "AttributeError" not in error_message
assert "has no attribute 'dest'" not in error_message

def test_invalid_short_option(self) -> None:
"""Test that invalid short option names produce helpful error messages."""
parser = Parser()

with pytest.raises(ArgumentError) as exc_info:
parser.addoption("-ab") # 3 chars, treated as invalid long option

error_message = str(exc_info.value)
# -ab is treated as invalid long option (3+ chars)
assert (
"invalid long option string" in error_message
or "invalid short option string" in error_message
)

def test_valid_option_works(self) -> None:
"""Test that valid options still work correctly."""
parser = Parser()
parser.addoption("--shuffle", action="store_true", help="Shuffle tests")

options = parser._anonymous.options
assert len(options) > 0
assert "--shuffle" in options[0].names()

def test_repr_with_dest_set(self) -> None:
"""Test that __repr__ works correctly when dest is set."""
parser = Parser()
parser.addoption("--valid-option", dest="valid_dest", help="A valid option")

# Get the argument object and check its repr
option = parser._anonymous.options[0]
repr_str = repr(option)

# Should contain the dest
assert "dest: 'valid_dest'" in repr_str
assert "NOT_SET" not in repr_str

def test_repr_without_dest(self) -> None:
"""Test that __repr__ works when dest is not set due to error."""
from _pytest.config.argparsing import Argument

# Create an Argument that will fail during initialization
# This triggers the code path where dest is not set
try:
Argument("invalid") # No dashes, will fail
except ArgumentError as exc:
# The repr was called during error creation
# Verify it contains NOT_SET representation
assert "dest:" in str(exc)
assert "NOT_SET" in str(exc) or "<notset>" in str(exc)