From 75ad0d9399e5397689de25d8e5c4baf81a24d93b Mon Sep 17 00:00:00 2001 From: Andrew Stitcher Date: Mon, 19 Aug 2024 14:40:15 -0400 Subject: [PATCH] Use the new disposition API in python WIP: Split functionality into local and remote disposition as each have different purposes and access to attributes. Add python tests Allow assigning remote dispositions to local delivery --- python/cproton.h | 5 + python/proton/__init__.py | 14 +- python/proton/_delivery.py | 409 +++++++++++++++++++++------- python/proton/_reactor.py | 10 +- python/tests/proton_tests/engine.py | 100 ++++++- 5 files changed, 421 insertions(+), 117 deletions(-) diff --git a/python/cproton.h b/python/cproton.h index f98915693..59fc89944 100644 --- a/python/cproton.h +++ b/python/cproton.h @@ -406,14 +406,19 @@ void pn_disposition_set_section_offset(pn_disposition_t *disposition, uint64_t s void pn_disposition_set_undeliverable(pn_disposition_t *disposition, _Bool undeliverable); uint64_t pn_disposition_type(pn_disposition_t *disposition); +typedef struct pn_custom_disposition_t pn_custom_disposition_t; typedef struct pn_received_disposition_t pn_received_disposition_t; typedef struct pn_rejected_disposition_t pn_rejected_disposition_t; typedef struct pn_modified_disposition_t pn_modified_disposition_t; +pn_custom_disposition_t *pn_custom_disposition(pn_disposition_t *disposition); pn_received_disposition_t *pn_received_disposition(pn_disposition_t *disposition); pn_rejected_disposition_t *pn_rejected_disposition(pn_disposition_t *disposition); pn_modified_disposition_t *pn_modified_disposition(pn_disposition_t *disposition); +void pn_custom_disposition_set_type(pn_custom_disposition_t *disposition, uint64_t type); +uint64_t pn_custom_disposition_get_type(pn_custom_disposition_t *disposition); +pn_data_t *pn_custom_disposition_data(pn_custom_disposition_t *disposition); pn_condition_t *pn_rejected_disposition_condition(pn_rejected_disposition_t *disposition); uint32_t pn_received_disposition_get_section_number(pn_received_disposition_t *disposition); void pn_received_disposition_set_section_number(pn_received_disposition_t *disposition, uint32_t section_number); diff --git a/python/proton/__init__.py b/python/proton/__init__.py index e63f256b3..7c87b9bfd 100644 --- a/python/proton/__init__.py +++ b/python/proton/__init__.py @@ -36,7 +36,8 @@ from ._condition import Condition from ._data import UNDESCRIBED, Array, Data, Described, char, symbol, timestamp, ubyte, ushort, uint, ulong, \ byte, short, int32, float32, decimal32, decimal64, decimal128, AnnotationDict, PropertyDict, SymbolList -from ._delivery import Delivery, Disposition, DispositionType +from ._delivery import Delivery, Disposition, DispositionType, GeneralDisposition, RejectedDisposition, \ + ModifiedDisposition, ReceivedDisposition from ._endpoints import Endpoint, Connection, Session, Link, Receiver, Sender, Terminus from ._events import Collector, Event, EventType from ._exceptions import ProtonException, MessageException, DataException, TransportException, \ @@ -47,8 +48,6 @@ from ._url import Url __all__ = [ - "API_LANGUAGE", - "IMPLEMENTATION_LANGUAGE", "UNDESCRIBED", "AnnotationDict", "Array", @@ -65,16 +64,18 @@ "Endpoint", "Event", "EventType", + "GeneralDisposition", "Handler", "Link", "LinkException", "Message", "MessageException", + "ModifiedDisposition", "PropertyDict", "ProtonException", - "VERSION_MAJOR", - "VERSION_MINOR", "Receiver", + "ReceivedDisposition", + "RejectedDisposition", "SASL", "Sender", "Session", @@ -111,9 +112,6 @@ VERSION_MINOR = PN_VERSION_MINOR VERSION_POINT = PN_VERSION_POINT VERSION = (VERSION_MAJOR, VERSION_MINOR, VERSION_POINT) -API_LANGUAGE = "C" -IMPLEMENTATION_LANGUAGE = "C" - handler = logging.NullHandler() diff --git a/python/proton/_delivery.py b/python/proton/_delivery.py index 4a224fb8f..d5b929a39 100644 --- a/python/proton/_delivery.py +++ b/python/proton/_delivery.py @@ -17,26 +17,44 @@ # under the License. # -from cproton import PN_ACCEPTED, PN_MODIFIED, PN_RECEIVED, PN_REJECTED, PN_RELEASED, pn_delivery_abort, \ - pn_delivery_aborted, pn_delivery_attachments, pn_delivery_link, pn_delivery_local, pn_delivery_local_state, \ - pn_delivery_partial, pn_delivery_pending, pn_delivery_readable, pn_delivery_remote, pn_delivery_remote_state, \ - pn_delivery_settle, pn_delivery_settled, pn_delivery_tag, pn_delivery_update, pn_delivery_updated, \ - pn_delivery_writable, pn_disposition_annotations, pn_disposition_condition, pn_disposition_data, \ - pn_disposition_get_section_number, pn_disposition_get_section_offset, pn_disposition_is_failed, \ - pn_disposition_is_undeliverable, pn_disposition_set_failed, pn_disposition_set_section_number, \ - pn_disposition_set_section_offset, pn_disposition_set_undeliverable, pn_disposition_type, \ - isnull - -from ._condition import cond2obj, obj2cond +from cproton import (PN_ACCEPTED, PN_MODIFIED, PN_RECEIVED, PN_REJECTED, PN_RELEASED, pn_delivery_abort, + pn_delivery_aborted, pn_delivery_attachments, pn_delivery_link, pn_delivery_local, + pn_delivery_local_state, + pn_delivery_partial, pn_delivery_pending, pn_delivery_readable, pn_delivery_remote, + pn_delivery_remote_state, + pn_delivery_settle, pn_delivery_settled, pn_delivery_tag, pn_delivery_update, pn_delivery_updated, + pn_delivery_writable, pn_disposition_annotations, pn_disposition_condition, pn_disposition_data, + pn_disposition_get_section_number, pn_disposition_get_section_offset, pn_disposition_is_failed, + pn_disposition_is_undeliverable, pn_disposition_set_failed, pn_disposition_set_section_number, + pn_disposition_set_section_offset, pn_disposition_set_undeliverable, pn_disposition_type, + pn_custom_disposition, + pn_custom_disposition_get_type, + pn_custom_disposition_set_type, + pn_custom_disposition_data, + pn_rejected_disposition, + pn_rejected_disposition_condition, + pn_received_disposition, + pn_received_disposition_get_section_number, + pn_received_disposition_set_section_number, + pn_received_disposition_get_section_offset, + pn_received_disposition_set_section_offset, + pn_modified_disposition, + pn_modified_disposition_is_failed, + pn_modified_disposition_set_failed, + pn_modified_disposition_is_undeliverable, + pn_modified_disposition_set_undeliverable, + pn_modified_disposition_annotations, + isnull) + +from ._condition import cond2obj, obj2cond, Condition from ._data import dat2obj, obj2dat from ._wrapper import Wrapper from enum import IntEnum -from typing import Dict, List, Optional, Union, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Self, Union, TYPE_CHECKING if TYPE_CHECKING: - from ._condition import Condition from ._data import PythonAMQPData, symbol from ._endpoints import Receiver, Sender # circular import from ._reactor import Connection, Session, Transport @@ -81,11 +99,11 @@ class DispositionType(IntEnum): """ @classmethod - def or_int(cls, i: int) -> Union[int, 'DispositionType']: + def or_int(cls, i: int) -> Union[int, Self]: return cls(i) if i in cls._value2member_map_ else i -class Disposition(object): +class Disposition: """ A delivery state. @@ -102,9 +120,127 @@ class Disposition(object): RELEASED = DispositionType.RELEASED MODIFIED = DispositionType.MODIFIED - def __init__(self, impl, local): - self._impl = impl - self.local = local + +class RemoteDisposition(Disposition): + + def __new__(cls, delivery_impl): + state = DispositionType.or_int(pn_delivery_remote_state(delivery_impl)) + if state == 0: + return None + elif state == cls.RECEIVED: + return super().__new__(RemoteReceivedDisposition) + elif state == cls.REJECTED: + return super().__new__(RemoteRejectedDisposition) + elif state == cls.MODIFIED: + return super().__new__(RemoteModifiedDisposition) + else: + return super().__new__(RemoteGeneralDisposition) + + +class RemoteGeneralDisposition(RemoteDisposition): + + def __init__(self, delivery_impl): + impl = pn_custom_disposition(pn_delivery_remote(delivery_impl)) + self._type = DispositionType.or_int(pn_custom_disposition_get_type(impl)) + self._data = dat2obj(pn_custom_disposition_data(impl)) + + @property + def type(self) -> Union[int, DispositionType]: + return self._type + + @property + def data(self) -> Optional[List[Any]]: + """Access the disposition as a :class:`Data` object. + + Dispositions are an extension point in the AMQP protocol. The + disposition interface provides setters/getters for those + dispositions that are predefined by the specification, however + access to the raw disposition data is provided so that other + dispositions can be used. + + The :class:`Data` object returned by this operation is valid until + the parent delivery is settled. + """ + r = self._data + return r if r != [] else None + + def apply_to(self, local_disposition: 'LocalDisposition'): + GeneralDisposition(self._type, self._data).apply_to(local_disposition) + + +class RemoteReceivedDisposition(RemoteDisposition): + + def __init__(self, delivery_impl): + impl = pn_received_disposition(pn_delivery_remote(delivery_impl)) + self._section_number = pn_received_disposition_get_section_number(impl) + self._section_offset = pn_received_disposition_get_section_offset(impl) + + @property + def type(self) -> Union[int, DispositionType]: + return Disposition.RECEIVED + + @property + def section_number(self) -> int: + return self._section_number + + @property + def section_offset(self) -> int: + return self._section_offset + + def apply_to(self, local_disposition: 'LocalDisposition'): + ReceivedDisposition(self._section_number, self._section_offset).apply_to(local_disposition) + + +class RemoteRejectedDisposition(RemoteDisposition): + + def __init__(self, delivery_impl): + impl = pn_rejected_disposition(pn_delivery_remote(delivery_impl)) + self._condition = cond2obj(pn_rejected_disposition_condition(impl)) + + @property + def type(self) -> Union[int, DispositionType]: + return Disposition.REJECTED + + @property + def condition(self) -> Optional[Condition]: + return self._condition + + def apply_to(self, local_disposition: 'LocalDisposition'): + RejectedDisposition(self._condition).apply_to(local_disposition) + + +class RemoteModifiedDisposition(RemoteDisposition): + + def __init__(self, delivery_impl): + impl = pn_modified_disposition(pn_delivery_remote(delivery_impl)) + self._annotations = dat2obj(pn_modified_disposition_annotations(impl)) + self._failed = pn_modified_disposition_is_failed(impl) + self._undeliverable = pn_modified_disposition_is_undeliverable(impl) + + @property + def type(self) -> Union[int, DispositionType]: + return Disposition.MODIFIED + + @property + def failed(self) -> bool: + return self._failed + + @property + def undeliverable(self) -> bool: + return self._undeliverable + + @property + def annotations(self) -> Optional[Dict['symbol', 'PythonAMQPData']]: + return self._annotations + + def apply_to(self, local_disposition: 'LocalDisposition'): + ModifiedDisposition(self._failed, self._undeliverable, self._annotations).apply_to(local_disposition) + + +class LocalDisposition(Disposition): + + def __init__(self, delivery_impl): + self._impl = pn_delivery_local(delivery_impl) self._data = None self._condition = None self._annotations = None @@ -124,9 +260,16 @@ def type(self) -> Union[int, DispositionType]: """ return DispositionType.or_int(pn_disposition_type(self._impl)) + @property + def data(self) -> Optional[List[int]]: + return self._data + + @data.setter + def data(self, obj: List[int]) -> None: + self._data = obj + @property def section_number(self) -> int: - """The section number associated with a disposition.""" return pn_disposition_get_section_number(self._impl) @section_number.setter @@ -135,16 +278,22 @@ def section_number(self, n: int) -> None: @property def section_offset(self) -> int: - """The section offset associated with a disposition.""" return pn_disposition_get_section_offset(self._impl) @section_offset.setter def section_offset(self, n: int) -> None: pn_disposition_set_section_offset(self._impl, n) + @property + def condition(self) -> Optional[Condition]: + return self._condition + + @condition.setter + def condition(self, obj: Condition) -> None: + self._condition = obj + @property def failed(self) -> bool: - """The failed flag for this disposition.""" return pn_disposition_is_failed(self._impl) @failed.setter @@ -153,7 +302,6 @@ def failed(self, b: bool) -> None: @property def undeliverable(self) -> bool: - """The undeliverable flag for this disposition.""" return pn_disposition_is_undeliverable(self._impl) @undeliverable.setter @@ -161,78 +309,125 @@ def undeliverable(self, b: bool) -> None: pn_disposition_set_undeliverable(self._impl, b) @property - def data(self) -> Optional[List[int]]: - """Access the disposition as a :class:`Data` object. + def annotations(self) -> Optional[Dict['symbol', 'PythonAMQPData']]: + return self._annotations - Dispositions are an extension point in the AMQP protocol. The - disposition interface provides setters/getters for those - dispositions that are predefined by the specification, however - access to the raw disposition data is provided so that other - dispositions can be used. + @annotations.setter + def annotations(self, obj: Dict['symbol', 'PythonAMQPData']) -> None: + self._annotations = obj - The :class:`Data` object returned by this operation is valid until - the parent delivery is settled. - """ - if self.local: - return self._data - else: - r = dat2obj(pn_disposition_data(self._impl)) - return r if r != [] else None - @data.setter - def data(self, obj: List[int]) -> None: - if self.local: - self._data = obj - else: - raise AttributeError("data attribute is read-only") +class ReceivedDisposition(LocalDisposition): + + def __init__(self, section_number: int = None, section_offset: int = None): + self._section_number = section_number + self._section_offset = section_offset @property - def annotations(self) -> Optional[Dict['symbol', 'PythonAMQPData']]: - """The annotations associated with a disposition. + def type(self) -> Union[int, DispositionType]: + return Disposition.RECEIVED - The :class:`Data` object retrieved by this operation may be modified - prior to updating a delivery. When a delivery is updated, the - annotations described by the :class:`Data` are reported to the peer - if applicable to the current delivery state, e.g. states such as - :const:`MODIFIED`. The :class:`Data` must be empty or contain a symbol - keyed map. + @property + def section_number(self) -> int: + return self._section_number - The :class:`Data` object returned by this operation is valid until - the parent delivery is settled. - """ - if self.local: - return self._annotations - else: - return dat2obj(pn_disposition_annotations(self._impl)) + @section_number.setter + def section_number(self, n: int) -> None: + self._section_number = n - @annotations.setter - def annotations(self, obj: Dict[str, 'PythonAMQPData']) -> None: - if self.local: - self._annotations = obj - else: - raise AttributeError("annotations attribute is read-only") + @property + def section_offset(self) -> int: + return self._section_offset + + @section_offset.setter + def section_offset(self, n: int) -> None: + self._section_offset = n + + def apply_to(self, local_disposition: LocalDisposition): + disp = pn_received_disposition(local_disposition._impl) + if self._section_number: + pn_received_disposition_set_section_number(disp, self._section_number) + if self._section_offset: + pn_received_disposition_set_section_offset(disp, self._section_offset) + + +class GeneralDisposition(LocalDisposition): + + def __init__(self, type: int, data: Any = None): + self._type = type + self._data = data + + def apply_to(self, local_disposition: LocalDisposition): + disp = pn_custom_disposition(local_disposition._impl) + pn_custom_disposition_set_type(disp, self._type) + obj2dat(self._data, pn_custom_disposition_data(disp)) + + +class RejectedDisposition(LocalDisposition): + + def __init__(self, condition: Optional[Condition] = None): + self._condition = condition @property - def condition(self) -> Optional['Condition']: - """The condition object associated with a disposition. + def type(self) -> Union[int, DispositionType]: + return Disposition.REJECTED - The :class:`Condition` object retrieved by this operation may be - modified prior to updating a delivery. When a delivery is updated, - the condition described by the disposition is reported to the peer - if applicable to the current delivery state, e.g. states such as - :const:`REJECTED`. - """ - if self.local: - return self._condition - else: - return cond2obj(pn_disposition_condition(self._impl)) + @property + def condition(self) -> Optional[Condition]: + return self._condition @condition.setter - def condition(self, obj: 'Condition') -> None: - if self.local: - self._condition = obj - else: - raise AttributeError("condition attribute is read-only") + def condition(self, obj: Condition) -> None: + self._condition = obj + + def apply_to(self, local_disposition: LocalDisposition): + disp = pn_rejected_disposition(local_disposition._impl) + obj2cond(self._condition, pn_rejected_disposition_condition(disp)) + + +class ModifiedDisposition(LocalDisposition): + + def __init__(self, failed: bool = None, undeliverable: bool = None, + annotations: Optional[Dict['symbol', 'PythonAMQPData']] = None): + self._failed = failed + self._undeliverable = undeliverable + self._annotations = annotations + + @property + def type(self) -> Union[int, DispositionType]: + return Disposition.MODIFIED + + @property + def failed(self) -> bool: + return self._failed + + @failed.setter + def failed(self, b: bool) -> None: + self._failed = b + + @property + def undeliverable(self) -> bool: + return self._undelivered + + @undeliverable.setter + def undeliverable(self, b: bool) -> None: + self._undelivered = b + + @property + def annotations(self) -> Optional[Dict['symbol', 'PythonAMQPData']]: + return self._annotations + + @annotations.setter + def annotations(self, obj: Dict['symbol', 'PythonAMQPData']) -> None: + self._annotations = obj + + def apply_to(self, local_disposition: LocalDisposition): + disp = pn_modified_disposition(local_disposition._impl) + if self._failed: + pn_modified_disposition_set_failed(disp, self._failed) + if self._undeliverable: + pn_modified_disposition_set_undeliverable(disp, self._undeliverable) + obj2dat(self._annotations, pn_modified_disposition_annotations(disp)) class Delivery(Wrapper): @@ -288,8 +483,26 @@ def __init__(self, impl): Wrapper.__init__(self, impl, pn_delivery_attachments) def _init(self) -> None: - self.local = Disposition(pn_delivery_local(self._impl), True) - self.remote = Disposition(pn_delivery_remote(self._impl), False) + self._local = None + self._remote = None + + @property + def remote(self) -> RemoteDisposition: + if self._remote is None: + self._remote = RemoteDisposition(self._impl) + return self._remote + + @property + def local(self) -> LocalDisposition: + if self._local is None: + self._local = LocalDisposition(self._impl) + return self._local + + @local.setter + def local(self, local_disposition: LocalDisposition) -> None: + if self._local is None: + self._local = LocalDisposition(self._impl) + local_disposition.apply_to(self._local) @property def tag(self) -> str: @@ -302,7 +515,7 @@ def tag(self) -> str: def writable(self) -> bool: """ ``True`` for an outgoing delivery to which data can now be written, - ``False`` otherwise.. + ``False`` otherwise. """ return pn_delivery_writable(self._impl) @@ -310,7 +523,7 @@ def writable(self) -> bool: def readable(self) -> bool: """ ``True`` for an incoming delivery that has data to read, - ``False`` otherwise.. + ``False`` otherwise. """ return pn_delivery_readable(self._impl) @@ -318,25 +531,29 @@ def readable(self) -> bool: def updated(self) -> bool: """ ``True`` if the state of the delivery has been updated - (e.g. it has been settled and/or accepted, rejected etc), + (e.g. it has been settled and/or accepted, rejected etc.), ``False`` otherwise. """ return pn_delivery_updated(self._impl) - def update(self, state: Union[int, DispositionType]) -> None: + def update(self, state: Union[int, DispositionType, None] = None) -> None: """ Set the local state of the delivery e.g. :const:`ACCEPTED`, :const:`REJECTED`, :const:`RELEASED`. - :param state: State of delivery - """ - if state == self.MODIFIED: - obj2dat(self.local._annotations, pn_disposition_annotations(self.local._impl)) - elif state == self.REJECTED: - obj2cond(self.local._condition, pn_disposition_condition(self.local._impl)) - elif state not in (self.ACCEPTED, self.RECEIVED, self.RELEASED): - obj2dat(self.local._data, pn_disposition_data(self.local._impl)) - pn_delivery_update(self._impl, state) + :param state: State of delivery, if omitted we assume the delivery state is already set + by other means + """ + if state: + if state == self.MODIFIED: + obj2dat(self.local._annotations, pn_disposition_annotations(self.local._impl)) + elif state == self.REJECTED: + obj2cond(self.local._condition, pn_disposition_condition(self.local._impl)) + elif state not in (self.ACCEPTED, self.RECEIVED, self.RELEASED): + obj2dat(self.local._data, pn_disposition_data(self.local._impl)) + pn_delivery_update(self._impl, state) + else: + pn_delivery_update(self._impl, self.local_state) @property def pending(self) -> int: diff --git a/python/proton/_reactor.py b/python/proton/_reactor.py index fc13135b6..2315edd25 100644 --- a/python/proton/_reactor.py +++ b/python/proton/_reactor.py @@ -44,7 +44,7 @@ class Literal(metaclass=GenericMeta): from cproton import PN_ACCEPTED, PN_EVENT_NONE from ._data import Described, symbol, ulong -from ._delivery import Delivery +from ._delivery import Delivery, GeneralDisposition from ._endpoints import Connection, Endpoint, Link, Session, Terminus from ._events import Collector, EventType, EventBase, Event from ._exceptions import SSLUnavailable @@ -595,8 +595,8 @@ def send( :return: Delivery object for this message. """ dlv = sender.send(msg, tag=tag) - dlv.local.data = [self.id] - dlv.update(0x34) + dlv.local = GeneralDisposition(0x34, [self.id]) + dlv.update() return dlv def accept(self, delivery: Delivery) -> None: @@ -613,8 +613,8 @@ def accept(self, delivery: Delivery) -> None: def update(self, delivery: Delivery, state: Optional[ulong] = None) -> None: if state: - delivery.local.data = [self.id, Described(ulong(state), [])] - delivery.update(0x34) + delivery.local = GeneralDisposition(0x34, [self.id, Described(ulong(state), [])]) + delivery.update() def _release_pending(self): for d in self._pending: diff --git a/python/tests/proton_tests/engine.py b/python/tests/proton_tests/engine.py index 7c191ba13..f628ba761 100644 --- a/python/tests/proton_tests/engine.py +++ b/python/tests/proton_tests/engine.py @@ -23,7 +23,8 @@ from typing import Union from proton import Array, Condition, Collector, Connection, Data, Delivery, Disposition, DispositionType, Endpoint, \ - Event, Link, PropertyDict, SASL, SessionException, SymbolList, Terminus, Transport, UNDESCRIBED, symbol + Event, GeneralDisposition, Link, ModifiedDisposition, PropertyDict, ReceivedDisposition, RejectedDisposition, \ + SASL, SessionException, SymbolList, Terminus, Transport, UNDESCRIBED, symbol from proton.reactor import Container from . import common @@ -2243,20 +2244,36 @@ def check(self, dlv: Delivery): class RejectedTester(DispositionTester): def __init__(self, condition: Condition): - self.condition = condition + self._condition = condition super().__init__(Disposition.REJECTED) def apply(self, dlv: Delivery): - dlv.local.condition = self.condition + dlv.local.condition = self._condition dlv.update(self._type) def check(self, dlv: Delivery): assert dlv.remote_state == self._type assert dlv.remote.type == self._type - assert dlv.remote.condition == self.condition, (dlv.condition, self.condition) + assert dlv.remote.condition == self._condition, (dlv.remote.condition, self._condition) -class ReceivedValue(DispositionTester): +class NewRejectedTester(DispositionTester): + + def __init__(self, condition: Condition): + self._condition = condition + super().__init__(Disposition.REJECTED) + + def apply(self, dlv: Delivery): + dlv.local = RejectedDisposition(self._condition) + dlv.update() + + def check(self, dlv: Delivery): + assert dlv.remote_state == self._type + assert dlv.remote.type == self._type + assert dlv.remote.condition == self._condition, (dlv.remote.condition, self._condition) + + +class ReceivedTester(DispositionTester): def __init__(self, section_number: int, section_offset: int): self._section_number = section_number self._section_offset = section_offset @@ -2270,10 +2287,27 @@ def apply(self, dlv: Delivery): def check(self, dlv: Delivery): assert dlv.remote_state == self._type assert dlv.remote.type == self._type - assert dlv.remote.section_number == self._section_number, (dlv.section_number, self._section_number) + assert dlv.remote.section_number == self._section_number, (dlv.remote.section_number, self._section_number) assert dlv.remote.section_offset == self._section_offset +class NewReceivedTester(DispositionTester): + def __init__(self, section_number: int, section_offset: int): + self._section_number = section_number + self._section_offset = section_offset + super().__init__(Disposition.RECEIVED) + + def apply(self, dlv: Delivery): + dlv.local = ReceivedDisposition(self._section_number, self._section_offset) + dlv.update() + + def check(self, dlv: Delivery): + assert dlv.remote_state == self._type + assert dlv.remote.type == self._type + assert dlv.remote.section_number == self._section_number, (dlv.remote.section_number, self._section_number) + assert dlv.remote.section_offset == self._section_offset, (dlv.remote.section_offset, self._section_offset) + + class ModifiedTester(DispositionTester): def __init__(self, failed, undeliverable, annotations): self._failed = failed @@ -2292,7 +2326,26 @@ def check(self, dlv: Delivery): assert dlv.remote.type == self._type assert dlv.remote.failed == self._failed assert dlv.remote.undeliverable == self._undeliverable - assert dlv.remote.annotations == self._annotations, (dlv.annotations, self._annotations) + assert dlv.remote.annotations == self._annotations, (dlv.remote.annotations, self._annotations) + + +class NewModifiedTester(DispositionTester): + def __init__(self, failed, undeliverable, annotations): + self._failed = failed + self._undeliverable = undeliverable + self._annotations = annotations + super().__init__(Disposition.MODIFIED) + + def apply(self, dlv: Delivery): + dlv.local = ModifiedDisposition(self._failed, self._undeliverable, self._annotations) + dlv.update() + + def check(self, dlv: Delivery): + assert dlv.remote_state == self._type + assert dlv.remote.type == self._type + assert dlv.remote.failed == self._failed + assert dlv.remote.undeliverable == self._undeliverable + assert dlv.remote.annotations == self._annotations, (dlv.remote.annotations, self._annotations) class CustomTester(DispositionTester): @@ -2310,6 +2363,21 @@ def check(self, dlv: Delivery): assert dlv.remote.data == self._data, (dlv.data, self._data) +class NewCustomTester(DispositionTester): + def __init__(self, type, data): + self._data = data + super().__init__(type) + + def apply(self, dlv: Delivery): + dlv.local = GeneralDisposition(self._type, self._data) + dlv.update() + + def check(self, dlv: Delivery): + assert dlv.remote_state == self._type + assert dlv.remote.type == self._type, (dlv.remote.type, self._type) + assert dlv.remote.data == self._data, (dlv.data, self._data) + + class DeliveryTest(Test): def tearDown(self): @@ -2366,11 +2434,17 @@ def testAccepted(self): self._testDisposition(Disposition.ACCEPTED) def testReceived(self): - self._testDisposition(ReceivedValue(1, 2)) + self._testDisposition(ReceivedTester(1, 2)) + + def testNewReceived(self): + self._testDisposition(NewReceivedTester(1, 2)) def testRejected(self): self._testDisposition(RejectedTester(Condition(symbol("foo")))) + def testNewRejected(self): + self._testDisposition(NewRejectedTester(Condition(symbol("foo")))) + def testReleased(self): self._testDisposition(Disposition.RELEASED) @@ -2378,15 +2452,25 @@ def testModified(self): self._testDisposition(ModifiedTester(failed=True, undeliverable=True, annotations={"key": "value"})) + def testNewModified(self): + self._testDisposition(NewModifiedTester(failed=True, undeliverable=True, + annotations={"key": "value"})) + def testCustom(self): self._testDisposition(CustomTester(0x12345, [1, 2, 3])) + def testNewCustom(self): + self._testDisposition(NewCustomTester(0x12345, [1, 2, 3])) + def testEmptyCustom(self): self._testDisposition(0x12345) def testNullCustom(self): self._testDisposition(CustomTester(0x12345, None)) + def testNullNewCustom(self): + self._testDisposition(NewCustomTester(0x12345, None)) + class CollectorTest(Test):