Skip to content

Commit

Permalink
test: Detect cython method signature variations in lxml < 5
Browse files Browse the repository at this point in the history
Positional arguments in lxml 4.x cython class methods don't have default values; they are only available since 5.0, thus check accordingly.
  • Loading branch information
abelcheung committed May 19, 2024
1 parent 61ba6c1 commit 4bd1d65
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 15 deletions.
4 changes: 4 additions & 0 deletions test-rt/_testutils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from types import ModuleType
from typing import NamedTuple

from lxml.etree import LXML_VERSION

is_lxml_4x = LXML_VERSION < (5, 0)


class FilePos(NamedTuple):
file: str
Expand Down
37 changes: 28 additions & 9 deletions test-rt/_testutils/decorator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import functools
from inspect import Signature, _ParameterKind, signature
from inspect import Parameter, Signature, _ParameterKind, signature, isclass
from typing import Any, Callable, ParamSpec, Sequence

import pytest
from .common import is_lxml_4x

_P = ParamSpec("_P")


# Very crude and probably won't work on many corner cases,
# but enough for here so far
def is_cython_class_method(func: Callable[..., Any]) -> bool:
glob = getattr(func, 'func_globals', None)
if not glob:
return False
parent = func.__qualname__.rsplit('.', 1)[0]
return isclass(glob[parent])


def signature_tester(
func_to_check: Callable[..., Any],
param_data: Sequence[tuple[str, _ParameterKind, Any] | None],
Expand All @@ -17,16 +28,24 @@ def wrapped(*args: _P.args, **kw: _P.kwargs) -> None:
sig = signature(func_to_check)
param = list(sig.parameters.values())
assert len(param) == len(param_data)

# For lxml < 5, args in class methods never contain
# default values (.__defaults__ property is empty).
# This is probably due to older cython
# compiler which doesn't support that yet
no_default = False
if is_lxml_4x and is_cython_class_method(func_to_check):
no_default = True

for i in range(len(param_data)):
if param_data[i] is None:
if (p := param_data[i]) is None:
continue
# fmt: off
assert (
param[i].name,
param[i].kind,
param[i].default
) == param_data[i]
# fmt: on
assert param[i].name == p[0]
assert param[i].kind == p[1]
if no_default:
assert param[i].default == Parameter.empty
else:
assert param[i].default == p[2]
f(*args, **kw)

return wrapped
Expand Down
9 changes: 3 additions & 6 deletions test-rt/test_etree_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,8 @@ def test_sequence_modify(self, xml_tree: _ElementTree) -> None:
@_testutils.signature_tester(_Element.index, (
None,
("child", Parameter.POSITIONAL_OR_KEYWORD, Parameter.empty),
("start", Parameter.POSITIONAL_OR_KEYWORD,
None if LXML_VERSION >= (5, 0) else Parameter.empty),
("stop" , Parameter.POSITIONAL_OR_KEYWORD,
None if LXML_VERSION >= (5, 0) else Parameter.empty),
("start", Parameter.POSITIONAL_OR_KEYWORD, None ),
("stop" , Parameter.POSITIONAL_OR_KEYWORD, None ),
))
# fmt: on
def test_method_index(self, xml_tree: _ElementTree) -> None:
Expand Down Expand Up @@ -281,8 +279,7 @@ def test_method_extend(self, xml_tree: _ElementTree) -> None:
# fmt: off
@_testutils.signature_tester(_Element.clear, (
None,
("keep_tail", Parameter.POSITIONAL_OR_KEYWORD,
False if LXML_VERSION >= (5, 0) else Parameter.empty),
("keep_tail", Parameter.POSITIONAL_OR_KEYWORD, False),
))
# fmt: on
def test_method_clear(self, xml_tree: _ElementTree) -> None:
Expand Down

0 comments on commit 4bd1d65

Please sign in to comment.