Merge pull request #7435 from bluetech/python-cleanups
python: a few cleanups
This commit is contained in:
commit
eced536eaf
|
@ -27,9 +27,6 @@ from _pytest.config import Config
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.warning_types import PytestUnknownMarkWarning
|
from _pytest.warning_types import PytestUnknownMarkWarning
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from _pytest.python import FunctionDefinition
|
|
||||||
|
|
||||||
|
|
||||||
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
|
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
|
||||||
|
|
||||||
|
@ -159,7 +156,7 @@ class ParameterSet(
|
||||||
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
||||||
func,
|
func,
|
||||||
config: Config,
|
config: Config,
|
||||||
function_definition: "FunctionDefinition",
|
nodeid: str,
|
||||||
) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]:
|
) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]:
|
||||||
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
|
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
|
||||||
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
|
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
|
||||||
|
@ -177,7 +174,7 @@ class ParameterSet(
|
||||||
)
|
)
|
||||||
fail(
|
fail(
|
||||||
msg.format(
|
msg.format(
|
||||||
nodeid=function_definition.nodeid,
|
nodeid=nodeid,
|
||||||
values=param.values,
|
values=param.values,
|
||||||
names=argnames,
|
names=argnames,
|
||||||
names_len=len(argnames),
|
names_len=len(argnames),
|
||||||
|
|
|
@ -209,16 +209,12 @@ def pytest_pycollect_makemodule(path: py.path.local, parent) -> "Module":
|
||||||
return mod
|
return mod
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(trylast=True)
|
||||||
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj):
|
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
|
||||||
outcome = yield
|
|
||||||
res = outcome.get_result()
|
|
||||||
if res is not None:
|
|
||||||
return
|
|
||||||
# nothing was collected elsewhere, let's do it here
|
# nothing was collected elsewhere, let's do it here
|
||||||
if safe_isclass(obj):
|
if safe_isclass(obj):
|
||||||
if collector.istestclass(obj, name):
|
if collector.istestclass(obj, name):
|
||||||
outcome.force_result(Class.from_parent(collector, name=name, obj=obj))
|
return Class.from_parent(collector, name=name, obj=obj)
|
||||||
elif collector.istestfunction(obj, name):
|
elif collector.istestfunction(obj, name):
|
||||||
# mock seems to store unbound methods (issue473), normalize it
|
# mock seems to store unbound methods (issue473), normalize it
|
||||||
obj = getattr(obj, "__func__", obj)
|
obj = getattr(obj, "__func__", obj)
|
||||||
|
@ -245,7 +241,7 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj):
|
||||||
res.warn(PytestCollectionWarning(reason))
|
res.warn(PytestCollectionWarning(reason))
|
||||||
else:
|
else:
|
||||||
res = list(collector._genfunctions(name, obj))
|
res = list(collector._genfunctions(name, obj))
|
||||||
outcome.force_result(res)
|
return res
|
||||||
|
|
||||||
|
|
||||||
class PyobjMixin:
|
class PyobjMixin:
|
||||||
|
@ -980,7 +976,7 @@ class Metafunc:
|
||||||
argvalues,
|
argvalues,
|
||||||
self.function,
|
self.function,
|
||||||
self.config,
|
self.config,
|
||||||
function_definition=self.definition,
|
nodeid=self.definition.nodeid,
|
||||||
)
|
)
|
||||||
del argvalues
|
del argvalues
|
||||||
|
|
||||||
|
@ -1003,7 +999,9 @@ class Metafunc:
|
||||||
if generated_ids is not None:
|
if generated_ids is not None:
|
||||||
ids = generated_ids
|
ids = generated_ids
|
||||||
|
|
||||||
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
|
ids = self._resolve_arg_ids(
|
||||||
|
argnames, ids, parameters, nodeid=self.definition.nodeid
|
||||||
|
)
|
||||||
|
|
||||||
# Store used (possibly generated) ids with parametrize Marks.
|
# Store used (possibly generated) ids with parametrize Marks.
|
||||||
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
|
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
|
||||||
|
@ -1042,7 +1040,7 @@ class Metafunc:
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
parameters: typing.Sequence[ParameterSet],
|
parameters: typing.Sequence[ParameterSet],
|
||||||
item,
|
nodeid: str,
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
|
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
|
||||||
to ``parametrize``.
|
to ``parametrize``.
|
||||||
|
@ -1050,7 +1048,7 @@ class Metafunc:
|
||||||
:param List[str] argnames: list of argument names passed to ``parametrize()``.
|
:param List[str] argnames: list of argument names passed to ``parametrize()``.
|
||||||
:param ids: the ids parameter of the parametrized call (see docs).
|
:param ids: the ids parameter of the parametrized call (see docs).
|
||||||
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
|
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
|
||||||
:param Item item: the item that generated this parametrized call.
|
:param str str: the nodeid of the item that generated this parametrized call.
|
||||||
:rtype: List[str]
|
:rtype: List[str]
|
||||||
:return: the list of ids for each argname given
|
:return: the list of ids for each argname given
|
||||||
"""
|
"""
|
||||||
|
@ -1063,7 +1061,7 @@ class Metafunc:
|
||||||
else:
|
else:
|
||||||
idfn = None
|
idfn = None
|
||||||
ids_ = self._validate_ids(ids, parameters, self.function.__name__)
|
ids_ = self._validate_ids(ids, parameters, self.function.__name__)
|
||||||
return idmaker(argnames, parameters, idfn, ids_, self.config, item=item)
|
return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)
|
||||||
|
|
||||||
def _validate_ids(
|
def _validate_ids(
|
||||||
self,
|
self,
|
||||||
|
@ -1223,7 +1221,7 @@ def _idval(
|
||||||
argname: str,
|
argname: str,
|
||||||
idx: int,
|
idx: int,
|
||||||
idfn: Optional[Callable[[object], Optional[object]]],
|
idfn: Optional[Callable[[object], Optional[object]]],
|
||||||
item,
|
nodeid: Optional[str],
|
||||||
config: Optional[Config],
|
config: Optional[Config],
|
||||||
) -> str:
|
) -> str:
|
||||||
if idfn:
|
if idfn:
|
||||||
|
@ -1232,8 +1230,9 @@ def _idval(
|
||||||
if generated_id is not None:
|
if generated_id is not None:
|
||||||
val = generated_id
|
val = generated_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}"
|
prefix = "{}: ".format(nodeid) if nodeid is not None else ""
|
||||||
msg = msg.format(item.nodeid, argname, idx)
|
msg = "error raised while trying to determine id of parameter '{}' at position {}"
|
||||||
|
msg = prefix + msg.format(argname, idx)
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
elif config:
|
elif config:
|
||||||
hook_id = config.hook.pytest_make_parametrize_id(
|
hook_id = config.hook.pytest_make_parametrize_id(
|
||||||
|
@ -1263,7 +1262,7 @@ def _idvalset(
|
||||||
argnames: Iterable[str],
|
argnames: Iterable[str],
|
||||||
idfn: Optional[Callable[[object], Optional[object]]],
|
idfn: Optional[Callable[[object], Optional[object]]],
|
||||||
ids: Optional[List[Union[None, str]]],
|
ids: Optional[List[Union[None, str]]],
|
||||||
item,
|
nodeid: Optional[str],
|
||||||
config: Optional[Config],
|
config: Optional[Config],
|
||||||
):
|
):
|
||||||
if parameterset.id is not None:
|
if parameterset.id is not None:
|
||||||
|
@ -1271,7 +1270,7 @@ def _idvalset(
|
||||||
id = None if ids is None or idx >= len(ids) else ids[idx]
|
id = None if ids is None or idx >= len(ids) else ids[idx]
|
||||||
if id is None:
|
if id is None:
|
||||||
this_id = [
|
this_id = [
|
||||||
_idval(val, argname, idx, idfn, item=item, config=config)
|
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
|
||||||
for val, argname in zip(parameterset.values, argnames)
|
for val, argname in zip(parameterset.values, argnames)
|
||||||
]
|
]
|
||||||
return "-".join(this_id)
|
return "-".join(this_id)
|
||||||
|
@ -1285,10 +1284,12 @@ def idmaker(
|
||||||
idfn: Optional[Callable[[object], Optional[object]]] = None,
|
idfn: Optional[Callable[[object], Optional[object]]] = None,
|
||||||
ids: Optional[List[Union[None, str]]] = None,
|
ids: Optional[List[Union[None, str]]] = None,
|
||||||
config: Optional[Config] = None,
|
config: Optional[Config] = None,
|
||||||
item=None,
|
nodeid: Optional[str] = None,
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
resolved_ids = [
|
resolved_ids = [
|
||||||
_idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item)
|
_idvalset(
|
||||||
|
valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
|
||||||
|
)
|
||||||
for valindex, parameterset in enumerate(parametersets)
|
for valindex, parameterset in enumerate(parametersets)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1589,9 +1590,8 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
|
|
||||||
# TODO: Type ignored -- breaks Liskov Substitution.
|
# TODO: Type ignored -- breaks Liskov Substitution.
|
||||||
def repr_failure( # type: ignore[override] # noqa: F821
|
def repr_failure( # type: ignore[override] # noqa: F821
|
||||||
self, excinfo: ExceptionInfo[BaseException], outerr: None = None
|
self, excinfo: ExceptionInfo[BaseException],
|
||||||
) -> Union[str, TerminalRepr]:
|
) -> Union[str, TerminalRepr]:
|
||||||
assert outerr is None, "XXX outerr usage is deprecated"
|
|
||||||
style = self.config.getoption("tbstyle", "auto")
|
style = self.config.getoption("tbstyle", "auto")
|
||||||
if style == "auto":
|
if style == "auto":
|
||||||
style = "long"
|
style = "long"
|
||||||
|
|
|
@ -19,6 +19,7 @@ from _pytest import python
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.pytester import Testdir
|
from _pytest.pytester import Testdir
|
||||||
from _pytest.python import _idval
|
from _pytest.python import _idval
|
||||||
|
from _pytest.python import idmaker
|
||||||
|
|
||||||
|
|
||||||
class TestMetafunc:
|
class TestMetafunc:
|
||||||
|
@ -35,10 +36,11 @@ class TestMetafunc:
|
||||||
@attr.s
|
@attr.s
|
||||||
class DefinitionMock(python.FunctionDefinition):
|
class DefinitionMock(python.FunctionDefinition):
|
||||||
obj = attr.ib()
|
obj = attr.ib()
|
||||||
|
_nodeid = attr.ib()
|
||||||
|
|
||||||
names = fixtures.getfuncargnames(func)
|
names = fixtures.getfuncargnames(func)
|
||||||
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
|
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
|
||||||
definition = DefinitionMock._create(func) # type: Any
|
definition = DefinitionMock._create(func, "mock::nodeid") # type: Any
|
||||||
return python.Metafunc(definition, fixtureinfo, config)
|
return python.Metafunc(definition, fixtureinfo, config)
|
||||||
|
|
||||||
def test_no_funcargs(self) -> None:
|
def test_no_funcargs(self) -> None:
|
||||||
|
@ -270,7 +272,7 @@ class TestMetafunc:
|
||||||
deadline=400.0
|
deadline=400.0
|
||||||
) # very close to std deadline and CI boxes are not reliable in CPU power
|
) # very close to std deadline and CI boxes are not reliable in CPU power
|
||||||
def test_idval_hypothesis(self, value) -> None:
|
def test_idval_hypothesis(self, value) -> None:
|
||||||
escaped = _idval(value, "a", 6, None, item=None, config=None)
|
escaped = _idval(value, "a", 6, None, nodeid=None, config=None)
|
||||||
assert isinstance(escaped, str)
|
assert isinstance(escaped, str)
|
||||||
escaped.encode("ascii")
|
escaped.encode("ascii")
|
||||||
|
|
||||||
|
@ -292,7 +294,7 @@ class TestMetafunc:
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
for val, expected in values:
|
for val, expected in values:
|
||||||
assert _idval(val, "a", 6, None, item=None, config=None) == expected
|
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
|
||||||
|
|
||||||
def test_unicode_idval_with_config(self) -> None:
|
def test_unicode_idval_with_config(self) -> None:
|
||||||
"""unittest for expected behavior to obtain ids with
|
"""unittest for expected behavior to obtain ids with
|
||||||
|
@ -321,7 +323,7 @@ class TestMetafunc:
|
||||||
("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
|
("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
|
||||||
] # type: List[Tuple[str, Any, str]]
|
] # type: List[Tuple[str, Any, str]]
|
||||||
for val, config, expected in values:
|
for val, config, expected in values:
|
||||||
actual = _idval(val, "a", 6, None, item=None, config=config)
|
actual = _idval(val, "a", 6, None, nodeid=None, config=config)
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
def test_bytes_idval(self) -> None:
|
def test_bytes_idval(self) -> None:
|
||||||
|
@ -338,7 +340,7 @@ class TestMetafunc:
|
||||||
("αρά".encode(), "\\xce\\xb1\\xcf\\x81\\xce\\xac"),
|
("αρά".encode(), "\\xce\\xb1\\xcf\\x81\\xce\\xac"),
|
||||||
]
|
]
|
||||||
for val, expected in values:
|
for val, expected in values:
|
||||||
assert _idval(val, "a", 6, idfn=None, item=None, config=None) == expected
|
assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected
|
||||||
|
|
||||||
def test_class_or_function_idval(self) -> None:
|
def test_class_or_function_idval(self) -> None:
|
||||||
"""unittest for the expected behavior to obtain ids for parametrized
|
"""unittest for the expected behavior to obtain ids for parametrized
|
||||||
|
@ -353,12 +355,10 @@ class TestMetafunc:
|
||||||
|
|
||||||
values = [(TestClass, "TestClass"), (test_function, "test_function")]
|
values = [(TestClass, "TestClass"), (test_function, "test_function")]
|
||||||
for val, expected in values:
|
for val, expected in values:
|
||||||
assert _idval(val, "a", 6, None, item=None, config=None) == expected
|
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
|
||||||
|
|
||||||
def test_idmaker_autoname(self) -> None:
|
def test_idmaker_autoname(self) -> None:
|
||||||
"""#250"""
|
"""#250"""
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)]
|
("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)]
|
||||||
)
|
)
|
||||||
|
@ -373,14 +373,10 @@ class TestMetafunc:
|
||||||
assert result == ["a0-\\xc3\\xb4"]
|
assert result == ["a0-\\xc3\\xb4"]
|
||||||
|
|
||||||
def test_idmaker_with_bytes_regex(self) -> None:
|
def test_idmaker_with_bytes_regex(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)])
|
result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)])
|
||||||
assert result == ["foo"]
|
assert result == ["foo"]
|
||||||
|
|
||||||
def test_idmaker_native_strings(self) -> None:
|
def test_idmaker_native_strings(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("a", "b"),
|
("a", "b"),
|
||||||
[
|
[
|
||||||
|
@ -414,8 +410,6 @@ class TestMetafunc:
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_idmaker_non_printable_characters(self) -> None:
|
def test_idmaker_non_printable_characters(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("s", "n"),
|
("s", "n"),
|
||||||
[
|
[
|
||||||
|
@ -430,8 +424,6 @@ class TestMetafunc:
|
||||||
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
|
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
|
||||||
|
|
||||||
def test_idmaker_manual_ids_must_be_printable(self) -> None:
|
def test_idmaker_manual_ids_must_be_printable(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("s",),
|
("s",),
|
||||||
[
|
[
|
||||||
|
@ -442,8 +434,6 @@ class TestMetafunc:
|
||||||
assert result == ["hello \\x00", "hello \\x05"]
|
assert result == ["hello \\x00", "hello \\x05"]
|
||||||
|
|
||||||
def test_idmaker_enum(self) -> None:
|
def test_idmaker_enum(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
enum = pytest.importorskip("enum")
|
enum = pytest.importorskip("enum")
|
||||||
e = enum.Enum("Foo", "one, two")
|
e = enum.Enum("Foo", "one, two")
|
||||||
result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
|
result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
|
||||||
|
@ -451,7 +441,6 @@ class TestMetafunc:
|
||||||
|
|
||||||
def test_idmaker_idfn(self) -> None:
|
def test_idmaker_idfn(self) -> None:
|
||||||
"""#351"""
|
"""#351"""
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
def ids(val: object) -> Optional[str]:
|
def ids(val: object) -> Optional[str]:
|
||||||
if isinstance(val, Exception):
|
if isinstance(val, Exception):
|
||||||
|
@ -471,7 +460,6 @@ class TestMetafunc:
|
||||||
|
|
||||||
def test_idmaker_idfn_unique_names(self) -> None:
|
def test_idmaker_idfn_unique_names(self) -> None:
|
||||||
"""#351"""
|
"""#351"""
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
def ids(val: object) -> str:
|
def ids(val: object) -> str:
|
||||||
return "a"
|
return "a"
|
||||||
|
@ -492,7 +480,6 @@ class TestMetafunc:
|
||||||
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
|
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
|
||||||
option. (#5294)
|
option. (#5294)
|
||||||
"""
|
"""
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
class MockConfig:
|
class MockConfig:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -525,7 +512,6 @@ class TestMetafunc:
|
||||||
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
|
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
|
||||||
option. (#5294)
|
option. (#5294)
|
||||||
"""
|
"""
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
class MockConfig:
|
class MockConfig:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -607,16 +593,12 @@ class TestMetafunc:
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_idmaker_with_ids(self) -> None:
|
def test_idmaker_with_ids(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None]
|
("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None]
|
||||||
)
|
)
|
||||||
assert result == ["a", "3-4"]
|
assert result == ["a", "3-4"]
|
||||||
|
|
||||||
def test_idmaker_with_paramset_id(self) -> None:
|
def test_idmaker_with_paramset_id(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("a", "b"),
|
("a", "b"),
|
||||||
[pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")],
|
[pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")],
|
||||||
|
@ -625,8 +607,6 @@ class TestMetafunc:
|
||||||
assert result == ["me", "you"]
|
assert result == ["me", "you"]
|
||||||
|
|
||||||
def test_idmaker_with_ids_unique_names(self) -> None:
|
def test_idmaker_with_ids_unique_names(self) -> None:
|
||||||
from _pytest.python import idmaker
|
|
||||||
|
|
||||||
result = idmaker(
|
result = idmaker(
|
||||||
("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"]
|
("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"]
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue