| import doxygen_constructors | |
| import inspect | |
| import comment_verifier | |
| from swig_test_utils import swig_check, swig_assert_raises | |
| def check_ab(obj, a, b): | |
| """Assert the constructor stored a and b correctly on the C++ instance.""" | |
| swig_check(obj.get_a(), a) | |
| swig_check(obj.get_b(), b) | |
| # Constructor docstrings reach __init__ in both -builtin and non-builtin. | |
| comment_verifier.check(inspect.getdoc(doxygen_constructors.ClassZeroArg.__init__), "Zero arg ctor doc.") | |
| comment_verifier.check(inspect.getdoc(doxygen_constructors.ClassOneArg.__init__), "One arg ctor doc.") | |
| comment_verifier.check(inspect.getdoc(doxygen_constructors.ClassTwoArg.__init__), "Two arg ctor doc.") | |
| comment_verifier.check(inspect.getdoc(doxygen_constructors.ClassKwArg.__init__), "Kwarg ctor doc.") | |
| # Positional construction; argument values reach the C++ members. | |
| doxygen_constructors.ClassZeroArg() | |
| swig_check(doxygen_constructors.ClassOneArg(11).get_a(), 11) | |
| check_ab(doxygen_constructors.ClassTwoArg(11, 2.5), 11, 2.5) | |
| check_ab(doxygen_constructors.ClassKwArg(11, 22), 11, 22) | |
| # Wrong arg count raises TypeError, identical between wrapping modes. | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassZeroArg(1) | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassOneArg() | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassOneArg(1, 2) | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassTwoArg(1) | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassTwoArg(1, 2.0, 3) | |
| # Without %feature("kwargs"), kwarg handling diverges between wrapping modes (long standing SWIG | |
| # behaviour, not specific to doxygen): the non-builtin shadow __init__ is a Python function and | |
| # accepts kwargs natively, while the builtin tp_init C wrapper only accepts positional args. The | |
| # %feature("kwargs") path below is where the two modes are expected to behave identically. | |
| # With %feature("kwargs"), kwarg order does not affect which value binds to which name, | |
| # and mixed positional/kwarg works. | |
| check_ab(doxygen_constructors.ClassKwArg(a=11, b=22), 11, 22) | |
| check_ab(doxygen_constructors.ClassKwArg(b=22, a=11), 11, 22) # reversed kwarg order | |
| check_ab(doxygen_constructors.ClassKwArg(11, b=22), 11, 22) # mixed positional + kwarg | |
| # Unknown kwarg name and missing arg both raise TypeError, identical in both modes. | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassKwArg(c=1, d=2) | |
| with swig_assert_raises(TypeError): | |
| doxygen_constructors.ClassKwArg(a=1) | |
| # Direct instantiation Cls(args) calls the tp_init slot and bypasses tp_dict["__init__"] | |
| # altogether, so it does not exercise the descriptor SWIG installs under -builtin to carry | |
| # the doxygen docstring. super().__init__() from a Python subclass is the realistic path | |
| # that does an attribute lookup and lands on that descriptor. The subclasses below run | |
| # that path for positional and keyword args. | |
| class TwoArgSubclass(doxygen_constructors.ClassTwoArg): | |
| def __init__(self, a, b, label): | |
| super().__init__(a, b) | |
| self.label = label | |
| class KwArgSubclass(doxygen_constructors.ClassKwArg): | |
| def __init__(self, **kw): | |
| super().__init__(**kw) | |
| sub = TwoArgSubclass(11, 2.5, "subclass-tag") | |
| swig_check(sub.get_a(), 11) | |
| swig_check(sub.get_b(), 2.5) | |
| swig_check(sub.label, "subclass-tag") | |
| check_ab(KwArgSubclass(a=11, b=22), 11, 22) | |
| check_ab(KwArgSubclass(b=22, a=11), 11, 22) | |
| # Confirm the descriptor SWIG installed actually replaced CPython's auto-installed slot | |
| # wrapper. PyType_Ready's add_operators populates tp_dict["__init__"] with a | |
| # wrapper_descriptor (type(object.__init__)) whose docstring is the fixed generic | |
| # "Initialize self." string. PyDescr_NewMethod produces a method_descriptor | |
| # (type(list.append)). Combined with the docstring checks above and the super().__init__() | |
| # calls above, this proves the wrapper is the call path under -builtin and not the default | |
| # slot wrapper. | |
| # Note: | |
| # >>> type(list.append).__name__ | |
| # 'method_descriptor' | |
| # >>> type(object.__init__).__name__ | |
| # 'wrapper_descriptor' | |
| if doxygen_constructors.is_python_builtin(): | |
| swig_check(type(doxygen_constructors.ClassZeroArg.__init__).__name__, "method_descriptor") | |
| swig_check(type(doxygen_constructors.ClassTwoArg.__init__).__name__, "method_descriptor") | |
| swig_check(type(doxygen_constructors.ClassKwArg.__init__).__name__, "method_descriptor") | |
| # Control: ClassNoDoc has no doxygen comment on its constructor, so SWIG does not emit | |
| # the __init__ descriptor override and PyType_Ready's auto-installed slot wrapper stays | |
| # in place. Its descriptor type matches type(object.__init__) and differs from the | |
| # documented classes above. | |
| swig_check(type(doxygen_constructors.ClassNoDoc.__init__).__name__, "wrapper_descriptor") | |
| swig_check(type(doxygen_constructors.ClassNoDoc.__init__), type(object.__init__)) | |
| swig_check(type(doxygen_constructors.ClassKwArg.__init__), type(list.append)) | |