test: drop the use of attr

All our uses can be done with dataclasses so we don't need an external
package.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/319>
This commit is contained in:
Peter Hutterer 2024-12-09 09:28:44 +10:00
parent f2811418dd
commit 504afdea4a
6 changed files with 76 additions and 74 deletions

View file

@ -296,7 +296,7 @@ abicheck@fedora:40:
meson compile -C _build meson compile -C _build
meson install -C _build meson install -C _build
popd popd
- pip install attrs - pip install attrs # required by libei 1.0.0
script: script:
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO - git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- git fetch --tags upstream$CI_JOB_ID - git fetch --tags upstream$CI_JOB_ID

View file

@ -296,7 +296,7 @@ abicheck@{{distro.name}}:{{version}}:
meson compile -C _build meson compile -C _build
meson install -C _build meson install -C _build
popd popd
- pip install attrs - pip install attrs # required by libei 1.0.0
script: script:
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO - git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- git fetch --tags upstream$CI_JOB_ID - git fetch --tags upstream$CI_JOB_ID

View file

@ -20,12 +20,12 @@
from typing import Any, Callable, Generator, Tuple from typing import Any, Callable, Generator, Tuple
from enum import IntEnum from enum import IntEnum
from dataclasses import dataclass, field
try: try:
from enum import StrEnum from enum import StrEnum
except ImportError: except ImportError:
from strenum import StrEnum from strenum import StrEnum
import attr
import binascii import binascii
import itertools import itertools
import logging import logging
@ -51,19 +51,24 @@ def hexlify(data):
return binascii.hexlify(data, sep=" ", bytes_per_sep=4) return binascii.hexlify(data, sep=" ", bytes_per_sep=4)
@attr.s class ObjectId(int):
def __repr__(self) -> str:
return f"{self:#x}"
@dataclass
class MethodCall: class MethodCall:
name: str = attr.ib() name: str
args: dict[str, Any] = attr.ib() args: dict[str, Any]
objects: dict[str, "Interface"] = attr.ib(default=attr.Factory(dict)) objects: dict[str, "Interface"] = field(default_factory=dict)
timestamp: float = attr.ib(default=attr.Factory(time.time)) timestamp: float = field(default_factory=time.time)
@attr.s @dataclass
class MessageHeader: class MessageHeader:
object_id: int = attr.ib(repr=lambda id: f"{id:#x}") object_id: ObjectId
msglen: int = attr.ib() msglen: int
opcode: int = attr.ib() opcode: int
@classmethod @classmethod
def size(cls) -> int: def size(cls) -> int:
@ -72,22 +77,21 @@ class MessageHeader:
@classmethod @classmethod
def from_data(cls, data: bytes) -> "MessageHeader": def from_data(cls, data: bytes) -> "MessageHeader":
object_id, msglen, opcode = struct.unpack("=QII", data[:cls.size()]) object_id, msglen, opcode = struct.unpack("=QII", data[:cls.size()])
return cls(object_id, msglen, opcode) return cls(ObjectId(object_id), msglen, opcode)
@property @property
def as_tuple(self) -> Tuple[int, int, int]: def as_tuple(self) -> Tuple[int, int, int]:
return self.object_id, self.msglen, self.opcode return self.object_id, self.msglen, self.opcode
@attr.s @dataclass
class Context: class Context:
objects: dict[str, "Interface"] = attr.ib(default=attr.Factory(dict)) objects: dict[str, "Interface"] = field(default_factory=dict)
_callbacks: dict[str, dict[int, Callable]] = attr.ib(init=False) _callbacks: dict[str, dict[int, Callable]] = field(
_ids: Generator = attr.ib(init=False, default=attr.Factory(itertools.count)) init=False,
default_factory=lambda: { "register": {}, "unregister": {}}
@_callbacks.default # type: ignore )
def _default_callbacks(self) -> dict[str, dict[int, Callable]]: _ids: Generator = field(init=False, default_factory=itertools.count)
return { "register": {}, "unregister": {}}
def register(self, object: "Interface") -> None: def register(self, object: "Interface") -> None:
assert object.object_id not in self.objects assert object.object_id not in self.objects
@ -147,15 +151,15 @@ class InterfaceName(StrEnum):
{% endfor %} {% endfor %}
@attr.s(eq=False) @dataclass(eq=False)
class Interface: class Interface:
object_id: int = attr.ib(repr=lambda id: f"{id:#x}") object_id: int
version: int = attr.ib() version: int
callbacks: dict[str, Callable] = attr.ib(init=False, default=attr.Factory(dict), repr=False) callbacks: dict[str, Callable] = field(init=False, default_factory=dict, repr=False)
calllog: list[MethodCall] = attr.ib(init=False, default=attr.Factory(list), repr=False) calllog: list[MethodCall] = field(init=False, default_factory=list, repr=False)
name: str = attr.ib(default="<overridden by subclass>") name: str = field(default="<overridden by subclass>")
incoming: dict[int, str] = attr.ib(default=attr.Factory(list), repr=False) incoming: dict[int, str] = field(default_factory=list, repr=False)
outgoing: dict[int, str] = attr.ib(default=attr.Factory(list), repr=False) outgoing: dict[int, str] = field(default_factory=list, repr=False)
def format(self, *args, opcode: int, signature: str) -> bytes: def format(self, *args, opcode: int, signature: str) -> bytes:
encoding = ["=QII"] encoding = ["=QII"]
@ -252,7 +256,7 @@ class Interface:
{% for interface in interfaces %} {% for interface in interfaces %}
@attr.s @dataclass
class {{interface.camel_name}}(Interface): class {{interface.camel_name}}(Interface):
{% for enum in interface.enums %} {% for enum in interface.enums %}
class {{component.capitalize()}}{{enum.camel_name}}(IntEnum): class {{component.capitalize()}}{{enum.camel_name}}(IntEnum):

View file

@ -144,7 +144,7 @@ endif
# Python-based tests # Python-based tests
pymod = import('python') pymod = import('python')
required_python_modules = ['pytest', 'attr', 'structlog'] required_python_modules = ['pytest', 'structlog']
python = pymod.find_installation('python3', required: get_option('tests')) python = pymod.find_installation('python3', required: get_option('tests'))
if python.found() and python.language_version().version_compare('< 3.11') if python.found() and python.language_version().version_compare('< 3.11')
required_python_modules += ['strenum'] required_python_modules += ['strenum']

View file

@ -49,8 +49,8 @@ from ctypes import c_char_p, c_int, c_uint32, c_void_p
from typing import Iterator, List, Tuple, Type, Optional, TextIO from typing import Iterator, List, Tuple, Type, Optional, TextIO
from gi.repository import GLib # type: ignore from gi.repository import GLib # type: ignore
from dbus.mainloop.glib import DBusGMainLoop from dbus.mainloop.glib import DBusGMainLoop
from dataclasses import dataclass
import attr
import ctypes import ctypes
import dbus import dbus
import dbus.proxies import dbus.proxies
@ -82,21 +82,21 @@ def version_at_least(have, required) -> bool:
return True return True
@attr.s @dataclass
class _Api: class _Api:
name: str = attr.ib() name: str
args: Tuple[Type[ctypes._SimpleCData], ...] = attr.ib() args: Tuple[Type[ctypes._SimpleCData], ...]
return_type: Optional[Type[ctypes._SimpleCData]] = attr.ib() return_type: Optional[Type[ctypes._SimpleCData]]
@property @property
def basename(self) -> str: def basename(self) -> str:
return self.name[len(PREFIX) :] return self.name[len(PREFIX) :]
@attr.s @dataclass
class _Enum: class _Enum:
name: str = attr.ib() name: str
value: int = attr.ib() value: int
@property @property
def basename(self) -> str: def basename(self) -> str:

View file

@ -23,8 +23,8 @@
from functools import reduce from functools import reduce
from typing import Generator, Optional from typing import Generator, Optional
from pathlib import Path from pathlib import Path
from dataclasses import dataclass, field
import attr
import itertools import itertools
import os import os
import pytest import pytest
@ -121,17 +121,17 @@ def eis(socketpath, eis_executable) -> Generator["Eis", None, None]:
eis.terminate() eis.terminate()
@attr.s @dataclass
class Ei: class Ei:
sock: socket.socket = attr.ib() sock: socket.socket
context: Context = attr.ib() context: Context
connection: Optional[EiConnection] = attr.ib(default=None) connection: Optional[EiConnection] = None
interface_versions: dict[str, int] = attr.ib(init=False, default=attr.Factory(dict)) interface_versions: dict[str, int] = field(init=False, default_factory=dict)
seats: list[EiSeat] = attr.ib(init=False, default=attr.Factory(list)) seats: list[EiSeat] = field(init=False, default_factory=list)
object_ids: Generator[int, None, None] = attr.ib( object_ids: Generator[int, None, None] = field(
init=False, default=attr.Factory(lambda: itertools.count(3)) init=False, default_factory=lambda: itertools.count(3)
) )
_data: bytes = attr.ib(init=False, default=attr.Factory(bytes)) # type: ignore _data: bytes = field(init=False, default_factory=bytes)
@property @property
def data(self) -> bytes: def data(self) -> bytes:
@ -314,12 +314,12 @@ class Ei:
return ei return ei
@attr.s @dataclass
class Eis: class Eis:
process: Optional[subprocess.Popen] = attr.ib() process: Optional[subprocess.Popen]
ei: Ei = attr.ib() ei: Ei
_stdout: Optional[str] = attr.ib(init=False, default=None) _stdout: Optional[str] = field(init=False, default=None)
_stderr: Optional[str] = attr.ib(init=False, default=None) _stderr: Optional[str] = field(init=False, default=None)
def terminate(self) -> None: def terminate(self) -> None:
if self.process is None: if self.process is None:
@ -829,12 +829,12 @@ class TestEiProtocol:
if iname != missing_interface: if iname != missing_interface:
ei.send(setup.InterfaceVersion(iname, VERSION_V(1))) ei.send(setup.InterfaceVersion(iname, VERSION_V(1)))
@attr.s @dataclass
class Status: class Status:
connected: bool = attr.ib(default=False) connected: bool = False
disconnected: bool = attr.ib(default=False) disconnected: bool = False
seats: bool = attr.ib(default=False) seats: bool = False
devices: bool = attr.ib(default=False) devices: bool = False
status = Status() status = Status()
@ -943,11 +943,11 @@ class TestEiProtocol:
ei.dispatch() ei.dispatch()
ei.wait_for_connection() ei.wait_for_connection()
@attr.s @dataclass
class Status: class Status:
disconnected: bool = attr.ib(default=False) disconnected: bool = False
reason: int = attr.ib(default=0) reason: int = 0
explanation: Optional[str] = attr.ib(default=None) explanation: Optional[str] = None
status = Status() status = Status()
@ -999,11 +999,11 @@ class TestEiProtocol:
ei.dispatch() ei.dispatch()
ei.wait_for_connection() ei.wait_for_connection()
@attr.s @dataclass
class Status: class Status:
disconnected: bool = attr.ib(default=False) disconnected: bool = False
reason: int = attr.ib(default=0) reason: int = 0
explanation: Optional[str] = attr.ib(default=None) explanation: Optional[str] = None
status = Status() status = Status()
@ -1039,9 +1039,9 @@ class TestEiProtocol:
""" """
ei = eis.ei ei = eis.ei
@attr.s @dataclass
class Status: class Status:
capability: Optional[Interface] = attr.ib(default=None) # type: ignore capability: Optional[Interface] = None # type: ignore
status = Status() status = Status()
@ -1101,12 +1101,10 @@ class TestEiProtocol:
""" """
ei = eis.ei ei = eis.ei
@attr.s @dataclass
class Status: class Status:
pointers: dict[InterfaceName, Interface] = attr.ib( pointers: dict[InterfaceName, Interface] = field(default_factory=dict)
default=attr.Factory(dict) all_caps: int = 0
) # type: ignore
all_caps: int = attr.ib(default=0)
status = Status() status = Status()