diff options
Diffstat (limited to 'src/python/grpcio_tests/tests/unit/framework/interfaces')
11 files changed, 31 insertions, 1910 deletions
diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/__init__.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/base/__init__.py deleted file mode 100644 index 7086519106..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_control.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_control.py deleted file mode 100644 index 0eb38abf22..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_control.py +++ /dev/null @@ -1,570 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Part of the tests of the base interface of RPC Framework.""" - -from __future__ import division - -import abc -import collections -import enum -import random # pylint: disable=unused-import -import threading -import time - -import six - -from grpc.framework.interfaces.base import base -from tests.unit.framework.common import test_constants -from tests.unit.framework.interfaces.base import _sequence -from tests.unit.framework.interfaces.base import _state -from tests.unit.framework.interfaces.base import test_interfaces # pylint: disable=unused-import - -_GROUP = 'base test cases test group' -_METHOD = 'base test cases test method' - -_PAYLOAD_RANDOM_SECTION_MAXIMUM_SIZE = test_constants.PAYLOAD_SIZE // 20 -_MINIMUM_PAYLOAD_SIZE = test_constants.PAYLOAD_SIZE // 600 - - -def _create_payload(randomness): - length = randomness.randint( - _MINIMUM_PAYLOAD_SIZE, test_constants.PAYLOAD_SIZE) - random_section_length = randomness.randint( - 0, min(_PAYLOAD_RANDOM_SECTION_MAXIMUM_SIZE, length)) - random_section = bytes( - bytearray( - randomness.getrandbits(8) for _ in range(random_section_length))) - sevens_section = b'\x07' * (length - random_section_length) - return b''.join(randomness.sample((random_section, sevens_section), 2)) - - -def _anything_in_flight(state): - return ( - state.invocation_initial_metadata_in_flight is not None or - state.invocation_payloads_in_flight or - state.invocation_completion_in_flight is not None or - state.service_initial_metadata_in_flight is not None or - state.service_payloads_in_flight or - state.service_completion_in_flight is not None or - 0 < state.invocation_allowance_in_flight or - 0 < state.service_allowance_in_flight - ) - - -def _verify_service_advance_and_update_state( - initial_metadata, payload, completion, allowance, state, implementation): - if initial_metadata is not None: - if state.invocation_initial_metadata_received: - return 'Later invocation initial metadata received: %s' % ( - initial_metadata,) - if state.invocation_payloads_received: - return 'Invocation initial metadata received after payloads: %s' % ( - state.invocation_payloads_received) - if state.invocation_completion_received: - return 'Invocation initial metadata received after invocation completion!' - if not implementation.metadata_transmitted( - state.invocation_initial_metadata_in_flight, initial_metadata): - return 'Invocation initial metadata maltransmitted: %s, %s' % ( - state.invocation_initial_metadata_in_flight, initial_metadata) - else: - state.invocation_initial_metadata_in_flight = None - state.invocation_initial_metadata_received = True - - if payload is not None: - if state.invocation_completion_received: - return 'Invocation payload received after invocation completion!' - elif not state.invocation_payloads_in_flight: - return 'Invocation payload "%s" received but not in flight!' % (payload,) - elif state.invocation_payloads_in_flight[0] != payload: - return 'Invocation payload mismatch: %s, %s' % ( - state.invocation_payloads_in_flight[0], payload) - elif state.service_side_invocation_allowance < 1: - return 'Disallowed invocation payload!' - else: - state.invocation_payloads_in_flight.pop(0) - state.invocation_payloads_received += 1 - state.service_side_invocation_allowance -= 1 - - if completion is not None: - if state.invocation_completion_received: - return 'Later invocation completion received: %s' % (completion,) - elif not implementation.completion_transmitted( - state.invocation_completion_in_flight, completion): - return 'Invocation completion maltransmitted: %s, %s' % ( - state.invocation_completion_in_flight, completion) - else: - state.invocation_completion_in_flight = None - state.invocation_completion_received = True - - if allowance is not None: - if allowance <= 0: - return 'Illegal allowance value: %s' % (allowance,) - else: - state.service_allowance_in_flight -= allowance - state.service_side_service_allowance += allowance - - -def _verify_invocation_advance_and_update_state( - initial_metadata, payload, completion, allowance, state, implementation): - if initial_metadata is not None: - if state.service_initial_metadata_received: - return 'Later service initial metadata received: %s' % (initial_metadata,) - if state.service_payloads_received: - return 'Service initial metadata received after service payloads: %s' % ( - state.service_payloads_received) - if state.service_completion_received: - return 'Service initial metadata received after service completion!' - if not implementation.metadata_transmitted( - state.service_initial_metadata_in_flight, initial_metadata): - return 'Service initial metadata maltransmitted: %s, %s' % ( - state.service_initial_metadata_in_flight, initial_metadata) - else: - state.service_initial_metadata_in_flight = None - state.service_initial_metadata_received = True - - if payload is not None: - if state.service_completion_received: - return 'Service payload received after service completion!' - elif not state.service_payloads_in_flight: - return 'Service payload "%s" received but not in flight!' % (payload,) - elif state.service_payloads_in_flight[0] != payload: - return 'Service payload mismatch: %s, %s' % ( - state.invocation_payloads_in_flight[0], payload) - elif state.invocation_side_service_allowance < 1: - return 'Disallowed service payload!' - else: - state.service_payloads_in_flight.pop(0) - state.service_payloads_received += 1 - state.invocation_side_service_allowance -= 1 - - if completion is not None: - if state.service_completion_received: - return 'Later service completion received: %s' % (completion,) - elif not implementation.completion_transmitted( - state.service_completion_in_flight, completion): - return 'Service completion maltransmitted: %s, %s' % ( - state.service_completion_in_flight, completion) - else: - state.service_completion_in_flight = None - state.service_completion_received = True - - if allowance is not None: - if allowance <= 0: - return 'Illegal allowance value: %s' % (allowance,) - else: - state.invocation_allowance_in_flight -= allowance - state.invocation_side_service_allowance += allowance - - -class Invocation( - collections.namedtuple( - 'Invocation', - ('group', 'method', 'subscription_kind', 'timeout', 'initial_metadata', - 'payload', 'completion',))): - """A description of operation invocation. - - Attributes: - group: The group identifier for the operation. - method: The method identifier for the operation. - subscription_kind: A base.Subscription.Kind value describing the kind of - subscription to use for the operation. - timeout: A duration in seconds to pass as the timeout value for the - operation. - initial_metadata: An object to pass as the initial metadata for the - operation or None. - payload: An object to pass as a payload value for the operation or None. - completion: An object to pass as a completion value for the operation or - None. - """ - - -class OnAdvance( - collections.namedtuple( - 'OnAdvance', - ('kind', 'initial_metadata', 'payload', 'completion', 'allowance'))): - """Describes action to be taken in a test in response to an advance call. - - Attributes: - kind: A Kind value describing the overall kind of response. - initial_metadata: An initial metadata value to pass to a call of the advance - method of the operator under test. Only valid if kind is Kind.ADVANCE and - may be None. - payload: A payload value to pass to a call of the advance method of the - operator under test. Only valid if kind is Kind.ADVANCE and may be None. - completion: A base.Completion value to pass to a call of the advance method - of the operator under test. Only valid if kind is Kind.ADVANCE and may be - None. - allowance: An allowance value to pass to a call of the advance method of the - operator under test. Only valid if kind is Kind.ADVANCE and may be None. - """ - - @enum.unique - class Kind(enum.Enum): - ADVANCE = 'advance' - DEFECT = 'defect' - IDLE = 'idle' - - -_DEFECT_ON_ADVANCE = OnAdvance(OnAdvance.Kind.DEFECT, None, None, None, None) -_IDLE_ON_ADVANCE = OnAdvance(OnAdvance.Kind.IDLE, None, None, None, None) - - -class Instruction( - collections.namedtuple( - 'Instruction', - ('kind', 'advance_args', 'advance_kwargs', 'conclude_success', - 'conclude_message', 'conclude_invocation_outcome_kind', - 'conclude_service_outcome_kind',))): - """""" - - @enum.unique - class Kind(enum.Enum): - ADVANCE = 'ADVANCE' - CANCEL = 'CANCEL' - CONCLUDE = 'CONCLUDE' - - -class Controller(six.with_metaclass(abc.ABCMeta)): - - @abc.abstractmethod - def failed(self, message): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def serialize_request(self, request): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def deserialize_request(self, serialized_request): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def serialize_response(self, response): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def deserialize_response(self, serialized_response): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def invocation(self): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def poll(self): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def on_service_advance( - self, initial_metadata, payload, completion, allowance): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def on_invocation_advance( - self, initial_metadata, payload, completion, allowance): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def service_on_termination(self, outcome): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def invocation_on_termination(self, outcome): - """""" - raise NotImplementedError() - - -class ControllerCreator(six.with_metaclass(abc.ABCMeta)): - - @abc.abstractmethod - def name(self): - """""" - raise NotImplementedError() - - @abc.abstractmethod - def controller(self, implementation, randomness): - """""" - raise NotImplementedError() - - -class _Remainder( - collections.namedtuple( - '_Remainder', - ('invocation_payloads', 'service_payloads', 'invocation_completion', - 'service_completion',))): - """Describes work remaining to be done in a portion of a test. - - Attributes: - invocation_payloads: The number of payloads to be sent from the invocation - side of the operation to the service side of the operation. - service_payloads: The number of payloads to be sent from the service side of - the operation to the invocation side of the operation. - invocation_completion: Whether or not completion from the invocation side of - the operation should be indicated and has yet to be indicated. - service_completion: Whether or not completion from the service side of the - operation should be indicated and has yet to be indicated. - """ - - -class _SequenceController(Controller): - - def __init__(self, sequence, implementation, randomness): - """Constructor. - - Args: - sequence: A _sequence.Sequence describing the steps to be taken in the - test at a relatively high level. - implementation: A test_interfaces.Implementation encapsulating the - base interface implementation that is the system under test. - randomness: A random.Random instance for use in the test. - """ - self._condition = threading.Condition() - self._sequence = sequence - self._implementation = implementation - self._randomness = randomness - - self._until = None - self._remaining_elements = None - self._poll_next = None - self._message = None - - self._state = _state.OperationState() - self._todo = None - - # called with self._condition - def _failed(self, message): - self._message = message - self._condition.notify_all() - - def _passed(self, invocation_outcome, service_outcome): - self._poll_next = Instruction( - Instruction.Kind.CONCLUDE, None, None, True, None, invocation_outcome, - service_outcome) - self._condition.notify_all() - - def failed(self, message): - with self._condition: - self._failed(message) - - def serialize_request(self, request): - return request + request - - def deserialize_request(self, serialized_request): - return serialized_request[:len(serialized_request) // 2] - - def serialize_response(self, response): - return response * 3 - - def deserialize_response(self, serialized_response): - return serialized_response[2 * len(serialized_response) // 3:] - - def invocation(self): - with self._condition: - self._until = time.time() + self._sequence.maximum_duration - self._remaining_elements = list(self._sequence.elements) - if self._sequence.invocation.initial_metadata: - initial_metadata = self._implementation.invocation_initial_metadata() - self._state.invocation_initial_metadata_in_flight = initial_metadata - else: - initial_metadata = None - if self._sequence.invocation.payload: - payload = _create_payload(self._randomness) - self._state.invocation_payloads_in_flight.append(payload) - else: - payload = None - if self._sequence.invocation.complete: - completion = self._implementation.invocation_completion() - self._state.invocation_completion_in_flight = completion - else: - completion = None - return Invocation( - _GROUP, _METHOD, base.Subscription.Kind.FULL, - self._sequence.invocation.timeout, initial_metadata, payload, - completion) - - def poll(self): - with self._condition: - while True: - if self._message is not None: - return Instruction( - Instruction.Kind.CONCLUDE, None, None, False, self._message, None, - None) - elif self._poll_next: - poll_next = self._poll_next - self._poll_next = None - return poll_next - elif self._until < time.time(): - return Instruction( - Instruction.Kind.CONCLUDE, None, None, False, - 'overran allotted time!', None, None) - else: - self._condition.wait(timeout=self._until-time.time()) - - def on_service_advance( - self, initial_metadata, payload, completion, allowance): - with self._condition: - message = _verify_service_advance_and_update_state( - initial_metadata, payload, completion, allowance, self._state, - self._implementation) - if message is not None: - self._failed(message) - if self._todo is not None: - raise ValueError('TODO!!!') - elif _anything_in_flight(self._state): - return _IDLE_ON_ADVANCE - elif self._remaining_elements: - element = self._remaining_elements.pop(0) - if element.kind is _sequence.Element.Kind.SERVICE_TRANSMISSION: - if element.transmission.initial_metadata: - initial_metadata = self._implementation.service_initial_metadata() - self._state.service_initial_metadata_in_flight = initial_metadata - else: - initial_metadata = None - if element.transmission.payload: - payload = _create_payload(self._randomness) - self._state.service_payloads_in_flight.append(payload) - self._state.service_side_service_allowance -= 1 - else: - payload = None - if element.transmission.complete: - completion = self._implementation.service_completion() - self._state.service_completion_in_flight = completion - else: - completion = None - if (not self._state.invocation_completion_received and - 0 <= self._state.service_side_invocation_allowance): - allowance = 1 - self._state.service_side_invocation_allowance += 1 - self._state.invocation_allowance_in_flight += 1 - else: - allowance = None - return OnAdvance( - OnAdvance.Kind.ADVANCE, initial_metadata, payload, completion, - allowance) - else: - raise ValueError('TODO!!!') - else: - return _IDLE_ON_ADVANCE - - def on_invocation_advance( - self, initial_metadata, payload, completion, allowance): - with self._condition: - message = _verify_invocation_advance_and_update_state( - initial_metadata, payload, completion, allowance, self._state, - self._implementation) - if message is not None: - self._failed(message) - if self._todo is not None: - raise ValueError('TODO!!!') - elif _anything_in_flight(self._state): - return _IDLE_ON_ADVANCE - elif self._remaining_elements: - element = self._remaining_elements.pop(0) - if element.kind is _sequence.Element.Kind.INVOCATION_TRANSMISSION: - if element.transmission.initial_metadata: - initial_metadata = self._implementation.invocation_initial_metadata() - self._state.invocation_initial_metadata_in_fight = initial_metadata - else: - initial_metadata = None - if element.transmission.payload: - payload = _create_payload(self._randomness) - self._state.invocation_payloads_in_flight.append(payload) - self._state.invocation_side_invocation_allowance -= 1 - else: - payload = None - if element.transmission.complete: - completion = self._implementation.invocation_completion() - self._state.invocation_completion_in_flight = completion - else: - completion = None - if (not self._state.service_completion_received and - 0 <= self._state.invocation_side_service_allowance): - allowance = 1 - self._state.invocation_side_service_allowance += 1 - self._state.service_allowance_in_flight += 1 - else: - allowance = None - return OnAdvance( - OnAdvance.Kind.ADVANCE, initial_metadata, payload, completion, - allowance) - else: - raise ValueError('TODO!!!') - else: - return _IDLE_ON_ADVANCE - - def service_on_termination(self, outcome): - with self._condition: - self._state.service_side_outcome = outcome - if self._todo is not None or self._remaining_elements: - self._failed('Premature service-side outcome %s!' % (outcome,)) - elif outcome.kind is not self._sequence.outcome_kinds.service: - self._failed( - 'Incorrect service-side outcome kind: %s should have been %s' % ( - outcome.kind, self._sequence.outcome_kinds.service)) - elif self._state.invocation_side_outcome is not None: - self._passed(self._state.invocation_side_outcome.kind, outcome.kind) - - def invocation_on_termination(self, outcome): - with self._condition: - self._state.invocation_side_outcome = outcome - if self._todo is not None or self._remaining_elements: - self._failed('Premature invocation-side outcome %s!' % (outcome,)) - elif outcome.kind is not self._sequence.outcome_kinds.invocation: - self._failed( - 'Incorrect invocation-side outcome kind: %s should have been %s' % ( - outcome.kind, self._sequence.outcome_kinds.invocation)) - elif self._state.service_side_outcome is not None: - self._passed(outcome.kind, self._state.service_side_outcome.kind) - - -class _SequenceControllerCreator(ControllerCreator): - - def __init__(self, sequence): - self._sequence = sequence - - def name(self): - return self._sequence.name - - def controller(self, implementation, randomness): - return _SequenceController(self._sequence, implementation, randomness) - - -CONTROLLER_CREATORS = tuple( - _SequenceControllerCreator(sequence) for sequence in _sequence.SEQUENCES) diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_sequence.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_sequence.py deleted file mode 100644 index 571d0e1e63..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_sequence.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Part of the tests of the base interface of RPC Framework.""" - -import collections -import enum - -from grpc.framework.interfaces.base import base -from tests.unit.framework.common import test_constants - - -class Invocation( - collections.namedtuple( - 'Invocation', ('timeout', 'initial_metadata', 'payload', 'complete',))): - """A recipe for operation invocation. - - Attributes: - timeout: A duration in seconds to pass to the system under test as the - operation's timeout value. - initial_metadata: A boolean indicating whether or not to pass initial - metadata when invoking the operation. - payload: A boolean indicating whether or not to pass a payload when - invoking the operation. - complete: A boolean indicating whether or not to indicate completion of - transmissions from the invoking side of the operation when invoking the - operation. - """ - - -class Transmission( - collections.namedtuple( - 'Transmission', ('initial_metadata', 'payload', 'complete',))): - """A recipe for a single transmission in an operation. - - Attributes: - initial_metadata: A boolean indicating whether or not to pass initial - metadata as part of the transmission. - payload: A boolean indicating whether or not to pass a payload as part of - the transmission. - complete: A boolean indicating whether or not to indicate completion of - transmission from the transmitting side of the operation as part of the - transmission. - """ - - -class Intertransmission( - collections.namedtuple('Intertransmission', ('invocation', 'service',))): - """A recipe for multiple transmissions in an operation. - - Attributes: - invocation: An integer describing the number of payloads to send from the - invocation side of the operation to the service side. - service: An integer describing the number of payloads to send from the - service side of the operation to the invocation side. - """ - - -class Element(collections.namedtuple('Element', ('kind', 'transmission',))): - """A sum type for steps to perform when testing an operation. - - Attributes: - kind: A Kind value describing the kind of step to perform in the test. - transmission: Only valid for kinds Kind.INVOCATION_TRANSMISSION and - Kind.SERVICE_TRANSMISSION, a Transmission value describing the details of - the transmission to be made. - """ - - @enum.unique - class Kind(enum.Enum): - INVOCATION_TRANSMISSION = 'invocation transmission' - SERVICE_TRANSMISSION = 'service transmission' - INTERTRANSMISSION = 'intertransmission' - INVOCATION_CANCEL = 'invocation cancel' - SERVICE_CANCEL = 'service cancel' - INVOCATION_FAILURE = 'invocation failure' - SERVICE_FAILURE = 'service failure' - - -class OutcomeKinds( - collections.namedtuple('Outcome', ('invocation', 'service',))): - """A description of the expected outcome of an operation test. - - Attributes: - invocation: The base.Outcome.Kind value expected on the invocation side of - the operation. - service: The base.Outcome.Kind value expected on the service side of the - operation. - """ - - -class Sequence( - collections.namedtuple( - 'Sequence', - ('name', 'maximum_duration', 'invocation', 'elements', - 'outcome_kinds',))): - """Describes at a high level steps to perform in a test. - - Attributes: - name: The string name of the sequence. - maximum_duration: A length of time in seconds to allow for the test before - declaring it to have failed. - invocation: An Invocation value describing how to invoke the operation - under test. - elements: A sequence of Element values describing at coarse granularity - actions to take during the operation under test. - outcome_kinds: An OutcomeKinds value describing the expected outcome kinds - of the test. - """ - -_EASY = Sequence( - 'Easy', - test_constants.TIME_ALLOWANCE, - Invocation(test_constants.LONG_TIMEOUT, True, True, True), - ( - Element( - Element.Kind.SERVICE_TRANSMISSION, Transmission(True, True, True)), - ), - OutcomeKinds(base.Outcome.Kind.COMPLETED, base.Outcome.Kind.COMPLETED)) - -_PEASY = Sequence( - 'Peasy', - test_constants.TIME_ALLOWANCE, - Invocation(test_constants.LONG_TIMEOUT, True, True, False), - ( - Element( - Element.Kind.SERVICE_TRANSMISSION, Transmission(True, True, False)), - Element( - Element.Kind.INVOCATION_TRANSMISSION, - Transmission(False, True, True)), - Element( - Element.Kind.SERVICE_TRANSMISSION, Transmission(False, True, True)), - ), - OutcomeKinds(base.Outcome.Kind.COMPLETED, base.Outcome.Kind.COMPLETED)) - - -# TODO(issue 2959): Finish this test suite. This tuple of sequences should -# contain at least the values in the Cartesian product of (half-duplex, -# full-duplex) * (zero payloads, one payload, test_constants.STREAM_LENGTH -# payloads) * (completion, cancellation, expiration, programming defect in -# servicer code). -SEQUENCES = ( - _EASY, - _PEASY, -) diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_state.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_state.py deleted file mode 100644 index 21cf33aeb6..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/_state.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Part of the tests of the base interface of RPC Framework.""" - - -class OperationState(object): - - def __init__(self): - self.invocation_initial_metadata_in_flight = None - self.invocation_initial_metadata_received = False - self.invocation_payloads_in_flight = [] - self.invocation_payloads_received = 0 - self.invocation_completion_in_flight = None - self.invocation_completion_received = False - self.service_initial_metadata_in_flight = None - self.service_initial_metadata_received = False - self.service_payloads_in_flight = [] - self.service_payloads_received = 0 - self.service_completion_in_flight = None - self.service_completion_received = False - self.invocation_side_invocation_allowance = 1 - self.invocation_side_service_allowance = 1 - self.service_side_invocation_allowance = 1 - self.service_side_service_allowance = 1 - self.invocation_allowance_in_flight = 0 - self.service_allowance_in_flight = 0 - self.invocation_side_outcome = None - self.service_side_outcome = None diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_cases.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_cases.py deleted file mode 100644 index 5d16bf98be..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_cases.py +++ /dev/null @@ -1,279 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests of the base interface of RPC Framework.""" - -from __future__ import division - -import logging -import random -import threading -import time -import unittest - -from grpc.framework.foundation import logging_pool -from grpc.framework.interfaces.base import base -from grpc.framework.interfaces.base import utilities -from tests.unit.framework.common import test_constants -from tests.unit.framework.interfaces.base import _control -from tests.unit.framework.interfaces.base import test_interfaces - -_SYNCHRONICITY_VARIATION = (('Sync', False), ('Async', True)) - -_EMPTY_OUTCOME_KIND_DICT = { - outcome_kind: 0 for outcome_kind in base.Outcome.Kind} - - -class _Serialization(test_interfaces.Serialization): - - def serialize_request(self, request): - return request + request - - def deserialize_request(self, serialized_request): - return serialized_request[:len(serialized_request) // 2] - - def serialize_response(self, response): - return response * 3 - - def deserialize_response(self, serialized_response): - return serialized_response[2 * len(serialized_response) // 3:] - - -def _advance(quadruples, operator, controller): - try: - for quadruple in quadruples: - operator.advance( - initial_metadata=quadruple[0], payload=quadruple[1], - completion=quadruple[2], allowance=quadruple[3]) - except Exception as e: # pylint: disable=broad-except - controller.failed('Exception on advance: %e' % e) - - -class _Operator(base.Operator): - - def __init__(self, controller, on_advance, pool, operator_under_test): - self._condition = threading.Condition() - self._controller = controller - self._on_advance = on_advance - self._pool = pool - self._operator_under_test = operator_under_test - self._pending_advances = [] - - def set_operator_under_test(self, operator_under_test): - with self._condition: - self._operator_under_test = operator_under_test - pent_advances = self._pending_advances - self._pending_advances = [] - pool = self._pool - controller = self._controller - - if pool is None: - _advance(pent_advances, operator_under_test, controller) - else: - pool.submit(_advance, pent_advances, operator_under_test, controller) - - def advance( - self, initial_metadata=None, payload=None, completion=None, - allowance=None): - on_advance = self._on_advance( - initial_metadata, payload, completion, allowance) - if on_advance.kind is _control.OnAdvance.Kind.ADVANCE: - with self._condition: - pool = self._pool - operator_under_test = self._operator_under_test - controller = self._controller - - quadruple = ( - on_advance.initial_metadata, on_advance.payload, - on_advance.completion, on_advance.allowance) - if pool is None: - _advance((quadruple,), operator_under_test, controller) - else: - pool.submit(_advance, (quadruple,), operator_under_test, controller) - elif on_advance.kind is _control.OnAdvance.Kind.DEFECT: - raise ValueError( - 'Deliberately raised exception from Operator.advance (in a test)!') - - -class _ProtocolReceiver(base.ProtocolReceiver): - - def __init__(self): - self._condition = threading.Condition() - self._contexts = [] - - def context(self, protocol_context): - with self._condition: - self._contexts.append(protocol_context) - - -class _Servicer(base.Servicer): - """A base.Servicer with instrumented for testing.""" - - def __init__(self, group, method, controllers, pool): - self._condition = threading.Condition() - self._group = group - self._method = method - self._pool = pool - self._controllers = list(controllers) - - def service(self, group, method, context, output_operator): - with self._condition: - controller = self._controllers.pop(0) - if group != self._group or method != self._method: - controller.fail( - '%s != %s or %s != %s' % (group, self._group, method, self._method)) - raise base.NoSuchMethodError(None, None) - else: - operator = _Operator( - controller, controller.on_service_advance, self._pool, - output_operator) - outcome = context.add_termination_callback( - controller.service_on_termination) - if outcome is not None: - controller.service_on_termination(outcome) - return utilities.full_subscription(operator, _ProtocolReceiver()) - - -class _OperationTest(unittest.TestCase): - - def setUp(self): - if self._synchronicity_variation: - self._pool = logging_pool.pool(test_constants.POOL_SIZE) - else: - self._pool = None - self._controller = self._controller_creator.controller( - self._implementation, self._randomness) - - def tearDown(self): - if self._synchronicity_variation: - self._pool.shutdown(wait=True) - else: - self._pool = None - - def test_operation(self): - invocation = self._controller.invocation() - if invocation.subscription_kind is base.Subscription.Kind.FULL: - test_operator = _Operator( - self._controller, self._controller.on_invocation_advance, - self._pool, None) - subscription = utilities.full_subscription( - test_operator, _ProtocolReceiver()) - else: - # TODO(nathaniel): support and test other subscription kinds. - self.fail('Non-full subscriptions not yet supported!') - - servicer = _Servicer( - invocation.group, invocation.method, (self._controller,), self._pool) - - invocation_end, service_end, memo = self._implementation.instantiate( - {(invocation.group, invocation.method): _Serialization()}, servicer) - - try: - invocation_end.start() - service_end.start() - operation_context, operator_under_test = invocation_end.operate( - invocation.group, invocation.method, subscription, invocation.timeout, - initial_metadata=invocation.initial_metadata, payload=invocation.payload, - completion=invocation.completion) - test_operator.set_operator_under_test(operator_under_test) - outcome = operation_context.add_termination_callback( - self._controller.invocation_on_termination) - if outcome is not None: - self._controller.invocation_on_termination(outcome) - except Exception as e: # pylint: disable=broad-except - self._controller.failed('Exception on invocation: %s' % e) - self.fail(e) - - while True: - instruction = self._controller.poll() - if instruction.kind is _control.Instruction.Kind.ADVANCE: - try: - test_operator.advance( - *instruction.advance_args, **instruction.advance_kwargs) - except Exception as e: # pylint: disable=broad-except - self._controller.failed('Exception on instructed advance: %s' % e) - elif instruction.kind is _control.Instruction.Kind.CANCEL: - try: - operation_context.cancel() - except Exception as e: # pylint: disable=broad-except - self._controller.failed('Exception on cancel: %s' % e) - elif instruction.kind is _control.Instruction.Kind.CONCLUDE: - break - - invocation_stop_event = invocation_end.stop(0) - service_stop_event = service_end.stop(0) - invocation_stop_event.wait() - service_stop_event.wait() - invocation_stats = invocation_end.operation_stats() - service_stats = service_end.operation_stats() - - self._implementation.destantiate(memo) - - self.assertTrue( - instruction.conclude_success, msg=instruction.conclude_message) - - expected_invocation_stats = dict(_EMPTY_OUTCOME_KIND_DICT) - expected_invocation_stats[ - instruction.conclude_invocation_outcome_kind] += 1 - self.assertDictEqual(expected_invocation_stats, invocation_stats) - expected_service_stats = dict(_EMPTY_OUTCOME_KIND_DICT) - expected_service_stats[instruction.conclude_service_outcome_kind] += 1 - self.assertDictEqual(expected_service_stats, service_stats) - - -def test_cases(implementation): - """Creates unittest.TestCase classes for a given Base implementation. - - Args: - implementation: A test_interfaces.Implementation specifying creation and - destruction of the Base implementation under test. - - Returns: - A sequence of subclasses of unittest.TestCase defining tests of the - specified Base layer implementation. - """ - random_seed = hash(time.time()) - logging.warning('Random seed for this execution: %s', random_seed) - randomness = random.Random(x=random_seed) - - test_case_classes = [] - for synchronicity_variation in _SYNCHRONICITY_VARIATION: - for controller_creator in _control.CONTROLLER_CREATORS: - name = ''.join( - (synchronicity_variation[0], controller_creator.name(), 'Test',)) - test_case_classes.append( - type(name, (_OperationTest,), - {'_implementation': implementation, - '_randomness': randomness, - '_synchronicity_variation': synchronicity_variation[1], - '_controller_creator': controller_creator, - '__module__': implementation.__module__, - })) - - return test_case_classes diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_interfaces.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_interfaces.py deleted file mode 100644 index 5eba475ba8..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_interfaces.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Interfaces used in tests of implementations of the Base layer.""" - -import abc - -import six - -from grpc.framework.interfaces.base import base # pylint: disable=unused-import - - -class Serialization(six.with_metaclass(abc.ABCMeta)): - """Specifies serialization and deserialization of test payloads.""" - - def serialize_request(self, request): - """Serializes a request value used in a test. - - Args: - request: A request value created by a test. - - Returns: - A bytestring that is the serialization of the given request. - """ - raise NotImplementedError() - - def deserialize_request(self, serialized_request): - """Deserializes a request value used in a test. - - Args: - serialized_request: A bytestring that is the serialization of some request - used in a test. - - Returns: - The request value encoded by the given bytestring. - """ - raise NotImplementedError() - - def serialize_response(self, response): - """Serializes a response value used in a test. - - Args: - response: A response value created by a test. - - Returns: - A bytestring that is the serialization of the given response. - """ - raise NotImplementedError() - - def deserialize_response(self, serialized_response): - """Deserializes a response value used in a test. - - Args: - serialized_response: A bytestring that is the serialization of some - response used in a test. - - Returns: - The response value encoded by the given bytestring. - """ - raise NotImplementedError() - - -class Implementation(six.with_metaclass(abc.ABCMeta)): - """Specifies an implementation of the Base layer.""" - - @abc.abstractmethod - def instantiate(self, serializations, servicer): - """Instantiates the Base layer implementation to be used in a test. - - Args: - serializations: A dict from group-method pair to Serialization object - specifying how to serialize and deserialize payload values used in the - test. - servicer: A base.Servicer object to be called to service RPCs made during - the test. - - Returns: - A sequence of length three the first element of which is a - base.End to be used to invoke RPCs, the second element of which is a - base.End to be used to service invoked RPCs, and the third element of - which is an arbitrary memo object to be kept and passed to destantiate - at the conclusion of the test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def destantiate(self, memo): - """Destroys the Base layer implementation under test. - - Args: - memo: The object from the third position of the return value of a call to - instantiate. - """ - raise NotImplementedError() - - @abc.abstractmethod - def invocation_initial_metadata(self): - """Provides an operation's invocation-side initial metadata. - - Returns: - A value to use for an operation's invocation-side initial metadata, or - None. - """ - raise NotImplementedError() - - @abc.abstractmethod - def service_initial_metadata(self): - """Provides an operation's service-side initial metadata. - - Returns: - A value to use for an operation's service-side initial metadata, or - None. - """ - raise NotImplementedError() - - @abc.abstractmethod - def invocation_completion(self): - """Provides an operation's invocation-side completion. - - Returns: - A base.Completion to use for an operation's invocation-side completion. - """ - raise NotImplementedError() - - @abc.abstractmethod - def service_completion(self): - """Provides an operation's service-side completion. - - Returns: - A base.Completion to use for an operation's service-side completion. - """ - raise NotImplementedError() - - @abc.abstractmethod - def metadata_transmitted(self, original_metadata, transmitted_metadata): - """Identifies whether or not metadata was properly transmitted. - - Args: - original_metadata: A metadata value passed to the system under test. - transmitted_metadata: The same metadata value after having been - transmitted through the system under test. - - Returns: - Whether or not the metadata was properly transmitted. - """ - raise NotImplementedError() - - @abc.abstractmethod - def completion_transmitted(self, original_completion, transmitted_completion): - """Identifies whether or not a base.Completion was properly transmitted. - - Args: - original_completion: A base.Completion passed to the system under test. - transmitted_completion: The same completion value after having been - transmitted through the system under test. - - Returns: - Whether or not the completion was properly transmitted. - """ - raise NotImplementedError() diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_future_invocation_asynchronous_event_service.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_future_invocation_asynchronous_event_service.py index 791620307b..df620b19ba 100644 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_future_invocation_asynchronous_event_service.py +++ b/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_future_invocation_asynchronous_event_service.py @@ -41,6 +41,7 @@ from concurrent import futures import six # test_interfaces is referenced from specification in this module. +from grpc.framework.foundation import future from grpc.framework.foundation import logging_pool from grpc.framework.interfaces.face import face from tests.unit.framework.common import test_constants @@ -159,6 +160,8 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. test_messages.verify(request, response, self) self.assertIs(callback.future(), response_future) + self.assertIsNone(response_future.exception()) + self.assertIsNone(response_future.traceback()) def testSuccessfulUnaryRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -191,6 +194,8 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. test_messages.verify(requests, response, self) self.assertIs(future_passed_to_callback, response_future) + self.assertIsNone(response_future.exception()) + self.assertIsNone(response_future.traceback()) def testSuccessfulStreamRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -301,6 +306,12 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. self.assertIs(callback.future(), response_future) self.assertFalse(cancel_method_return_value) self.assertTrue(response_future.cancelled()) + with self.assertRaises(future.CancelledError): + response_future.result() + with self.assertRaises(future.CancelledError): + response_future.exception() + with self.assertRaises(future.CancelledError): + response_future.traceback() def testCancelledUnaryRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -332,6 +343,12 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. self.assertIs(callback.future(), response_future) self.assertFalse(cancel_method_return_value) self.assertTrue(response_future.cancelled()) + with self.assertRaises(future.CancelledError): + response_future.result() + with self.assertRaises(future.CancelledError): + response_future.exception() + with self.assertRaises(future.CancelledError): + response_future.traceback() def testCancelledStreamRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -363,6 +380,9 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. response_future.exception(), face.ExpirationError) with self.assertRaises(face.ExpirationError): response_future.result() + self.assertIsInstance( + response_future.exception(), face.AbortionError) + self.assertIsNotNone(response_future.traceback()) def testExpiredUnaryRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -392,6 +412,9 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. response_future.exception(), face.ExpirationError) with self.assertRaises(face.ExpirationError): response_future.result() + self.assertIsInstance( + response_future.exception(), face.AbortionError) + self.assertIsNotNone(response_future.traceback()) def testExpiredStreamRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -411,11 +434,13 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. for test_messages in test_messages_sequence: request = test_messages.request() callback = _Callback() + abortion_callback = _Callback() with self._control.fail(): response_future = self._invoker.future(group, method)( request, _3069_test_constant.REALLY_SHORT_TIMEOUT) response_future.add_done_callback(callback) + response_future.add_abortion_callback(abortion_callback) self.assertIs(callback.future(), response_future) # Because the servicer fails outside of the thread from which the @@ -426,6 +451,8 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. response_future.exception(), face.ExpirationError) with self.assertRaises(face.ExpirationError): response_future.result() + self.assertIsNotNone(response_future.traceback()) + self.assertIsNotNone(abortion_callback.future()) def testFailedUnaryRequestStreamResponse(self): for (group, method), test_messages_sequence in ( @@ -448,11 +475,13 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. for test_messages in test_messages_sequence: requests = test_messages.requests() callback = _Callback() + abortion_callback = _Callback() with self._control.fail(): response_future = self._invoker.future(group, method)( iter(requests), _3069_test_constant.REALLY_SHORT_TIMEOUT) response_future.add_done_callback(callback) + response_future.add_abortion_callback(abortion_callback) self.assertIs(callback.future(), response_future) # Because the servicer fails outside of the thread from which the @@ -463,6 +492,8 @@ class TestCase(six.with_metaclass(abc.ABCMeta, test_coverage.Coverage, unittest. response_future.exception(), face.ExpirationError) with self.assertRaises(face.ExpirationError): response_future.result() + self.assertIsNotNone(response_future.traceback()) + self.assertIsNotNone(abortion_callback.future()) def testFailedStreamRequestStreamResponse(self): for (group, method), test_messages_sequence in ( diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_receiver.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_receiver.py deleted file mode 100644 index 48f31fc677..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/face/_receiver.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""A utility useful in tests of asynchronous, event-driven interfaces.""" - -import threading - -from grpc.framework.interfaces.face import face - - -class Receiver(face.ResponseReceiver): - """A utility object useful in tests of asynchronous code.""" - - def __init__(self): - self._condition = threading.Condition() - self._initial_metadata = None - self._responses = [] - self._terminal_metadata = None - self._code = None - self._details = None - self._completed = False - self._abortion = None - - def abort(self, abortion): - with self._condition: - self._abortion = abortion - self._condition.notify_all() - - def initial_metadata(self, initial_metadata): - with self._condition: - self._initial_metadata = initial_metadata - - def response(self, response): - with self._condition: - self._responses.append(response) - - def complete(self, terminal_metadata, code, details): - with self._condition: - self._terminal_metadata = terminal_metadata - self._code = code - self._details = details - self._completed = True - self._condition.notify_all() - - def block_until_terminated(self): - with self._condition: - while self._abortion is None and not self._completed: - self._condition.wait() - - def unary_response(self): - with self._condition: - if self._abortion is not None: - raise AssertionError('Aborted: "{}"!'.format(self._abortion)) - elif len(self._responses) != 1: - raise AssertionError( - '%d responses received, not exactly one!', len(self._responses)) - else: - return self._responses[0] - - def stream_responses(self): - with self._condition: - if self._abortion is None: - return list(self._responses) - else: - raise AssertionError('Aborted: "{}"!'.format(self._abortion)) - - def abortion(self): - with self._condition: - return self._abortion diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/links/__init__.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/links/__init__.py deleted file mode 100644 index 7086519106..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/links/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_cases.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_cases.py deleted file mode 100644 index 608e64119e..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_cases.py +++ /dev/null @@ -1,327 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests of the links interface of RPC Framework.""" - -# unittest is referenced from specification in this module. -import abc -import unittest # pylint: disable=unused-import - -import six - -from grpc.framework.interfaces.links import links -from tests.unit.framework.common import test_constants -from tests.unit.framework.interfaces.links import test_utilities - - -def at_least_n_payloads_received_predicate(n): - def predicate(ticket_sequence): - payload_count = 0 - for ticket in ticket_sequence: - if ticket.payload is not None: - payload_count += 1 - if n <= payload_count: - return True - else: - return False - return predicate - - -def terminated(ticket_sequence): - return ticket_sequence and ticket_sequence[-1].termination is not None - -_TRANSMISSION_GROUP = 'test.Group' -_TRANSMISSION_METHOD = 'TestMethod' - - -class TransmissionTest(six.with_metaclass(abc.ABCMeta)): - """Tests ticket transmission between two connected links. - - This class must be mixed into a unittest.TestCase that implements the abstract - methods it provides. - """ - - # This is a unittest.TestCase mix-in. - # pylint: disable=invalid-name - - @abc.abstractmethod - def create_transmitting_links(self): - """Creates two connected links for use in this test. - - Returns: - Two links.Links, the first of which will be used on the invocation side - of RPCs and the second of which will be used on the service side of - RPCs. - """ - raise NotImplementedError() - - @abc.abstractmethod - def destroy_transmitting_links(self, invocation_side_link, service_side_link): - """Destroys the two connected links created for this test. - - - Args: - invocation_side_link: The link used on the invocation side of RPCs in - this test. - service_side_link: The link used on the service side of RPCs in this - test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def create_invocation_initial_metadata(self): - """Creates a value for use as invocation-side initial metadata. - - Returns: - A metadata value appropriate for use as invocation-side initial metadata - or None if invocation-side initial metadata transmission is not - supported by the links under test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def create_invocation_terminal_metadata(self): - """Creates a value for use as invocation-side terminal metadata. - - Returns: - A metadata value appropriate for use as invocation-side terminal - metadata or None if invocation-side terminal metadata transmission is - not supported by the links under test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def create_service_initial_metadata(self): - """Creates a value for use as service-side initial metadata. - - Returns: - A metadata value appropriate for use as service-side initial metadata or - None if service-side initial metadata transmission is not supported by - the links under test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def create_service_terminal_metadata(self): - """Creates a value for use as service-side terminal metadata. - - Returns: - A metadata value appropriate for use as service-side terminal metadata or - None if service-side terminal metadata transmission is not supported by - the links under test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def create_invocation_completion(self): - """Creates values for use as invocation-side code and message. - - Returns: - An invocation-side code value and an invocation-side message value. - Either or both may be None if invocation-side code and/or - invocation-side message transmission is not supported by the links - under test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def create_service_completion(self): - """Creates values for use as service-side code and message. - - Returns: - A service-side code value and a service-side message value. Either or - both may be None if service-side code and/or service-side message - transmission is not supported by the links under test. - """ - raise NotImplementedError() - - @abc.abstractmethod - def assertMetadataTransmitted(self, original_metadata, transmitted_metadata): - """Asserts that transmitted_metadata contains original_metadata. - - Args: - original_metadata: A metadata object used in this test. - transmitted_metadata: A metadata object obtained after transmission - through the system under test. - - Raises: - AssertionError: if the transmitted_metadata object does not contain - original_metadata. - """ - raise NotImplementedError() - - def group_and_method(self): - """Returns the group and method used in this test case. - - Returns: - A pair of the group and method used in this test case. - """ - return _TRANSMISSION_GROUP, _TRANSMISSION_METHOD - - def serialize_request(self, request): - """Serializes a request value used in this test case. - - Args: - request: A request value created by this test case. - - Returns: - A bytestring that is the serialization of the given request. - """ - return request - - def deserialize_request(self, serialized_request): - """Deserializes a request value used in this test case. - - Args: - serialized_request: A bytestring that is the serialization of some request - used in this test case. - - Returns: - The request value encoded by the given bytestring. - """ - return serialized_request - - def serialize_response(self, response): - """Serializes a response value used in this test case. - - Args: - response: A response value created by this test case. - - Returns: - A bytestring that is the serialization of the given response. - """ - return response - - def deserialize_response(self, serialized_response): - """Deserializes a response value used in this test case. - - Args: - serialized_response: A bytestring that is the serialization of some - response used in this test case. - - Returns: - The response value encoded by the given bytestring. - """ - return serialized_response - - def _assert_is_valid_metadata_payload_sequence( - self, ticket_sequence, payloads, initial_metadata, terminal_metadata): - initial_metadata_seen = False - seen_payloads = [] - terminal_metadata_seen = False - - for ticket in ticket_sequence: - if ticket.initial_metadata is not None: - self.assertFalse(initial_metadata_seen) - self.assertFalse(seen_payloads) - self.assertFalse(terminal_metadata_seen) - self.assertMetadataTransmitted(initial_metadata, ticket.initial_metadata) - initial_metadata_seen = True - - if ticket.payload is not None: - self.assertFalse(terminal_metadata_seen) - seen_payloads.append(ticket.payload) - - if ticket.terminal_metadata is not None: - self.assertFalse(terminal_metadata_seen) - self.assertMetadataTransmitted(terminal_metadata, ticket.terminal_metadata) - terminal_metadata_seen = True - self.assertSequenceEqual(payloads, seen_payloads) - - def _assert_is_valid_invocation_sequence( - self, ticket_sequence, group, method, payloads, initial_metadata, - terminal_metadata, termination): - self.assertLess(0, len(ticket_sequence)) - self.assertEqual(group, ticket_sequence[0].group) - self.assertEqual(method, ticket_sequence[0].method) - self._assert_is_valid_metadata_payload_sequence( - ticket_sequence, payloads, initial_metadata, terminal_metadata) - self.assertIs(termination, ticket_sequence[-1].termination) - - def _assert_is_valid_service_sequence( - self, ticket_sequence, payloads, initial_metadata, terminal_metadata, - code, message, termination): - self.assertLess(0, len(ticket_sequence)) - self._assert_is_valid_metadata_payload_sequence( - ticket_sequence, payloads, initial_metadata, terminal_metadata) - self.assertEqual(code, ticket_sequence[-1].code) - self.assertEqual(message, ticket_sequence[-1].message) - self.assertIs(termination, ticket_sequence[-1].termination) - - def setUp(self): - self._invocation_link, self._service_link = self.create_transmitting_links() - self._invocation_mate = test_utilities.RecordingLink() - self._service_mate = test_utilities.RecordingLink() - self._invocation_link.join_link(self._invocation_mate) - self._service_link.join_link(self._service_mate) - - def tearDown(self): - self.destroy_transmitting_links(self._invocation_link, self._service_link) - - def testSimplestRoundTrip(self): - """Tests transmission of one ticket in each direction.""" - invocation_operation_id = object() - invocation_payload = b'\x07' * 1023 - timeout = test_constants.LONG_TIMEOUT - invocation_initial_metadata = self.create_invocation_initial_metadata() - invocation_terminal_metadata = self.create_invocation_terminal_metadata() - invocation_code, invocation_message = self.create_invocation_completion() - service_payload = b'\x08' * 1025 - service_initial_metadata = self.create_service_initial_metadata() - service_terminal_metadata = self.create_service_terminal_metadata() - service_code, service_message = self.create_service_completion() - - original_invocation_ticket = links.Ticket( - invocation_operation_id, 0, _TRANSMISSION_GROUP, _TRANSMISSION_METHOD, - links.Ticket.Subscription.FULL, timeout, 0, invocation_initial_metadata, - invocation_payload, invocation_terminal_metadata, invocation_code, - invocation_message, links.Ticket.Termination.COMPLETION, None) - self._invocation_link.accept_ticket(original_invocation_ticket) - - self._service_mate.block_until_tickets_satisfy( - at_least_n_payloads_received_predicate(1)) - service_operation_id = self._service_mate.tickets()[0].operation_id - - self._service_mate.block_until_tickets_satisfy(terminated) - self._assert_is_valid_invocation_sequence( - self._service_mate.tickets(), _TRANSMISSION_GROUP, _TRANSMISSION_METHOD, - (invocation_payload,), invocation_initial_metadata, - invocation_terminal_metadata, links.Ticket.Termination.COMPLETION) - - original_service_ticket = links.Ticket( - service_operation_id, 0, None, None, links.Ticket.Subscription.FULL, - timeout, 0, service_initial_metadata, service_payload, - service_terminal_metadata, service_code, service_message, - links.Ticket.Termination.COMPLETION, None) - self._service_link.accept_ticket(original_service_ticket) - self._invocation_mate.block_until_tickets_satisfy(terminated) - self._assert_is_valid_service_sequence( - self._invocation_mate.tickets(), (service_payload,), - service_initial_metadata, service_terminal_metadata, service_code, - service_message, links.Ticket.Termination.COMPLETION) diff --git a/src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_utilities.py b/src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_utilities.py deleted file mode 100644 index 39c7f2fc63..0000000000 --- a/src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_utilities.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""State and behavior appropriate for use in tests.""" - -import logging -import threading -import time - -from grpc.framework.interfaces.links import links -from grpc.framework.interfaces.links import utilities - -# A more-or-less arbitrary limit on the length of raw data values to be logged. -_UNCOMFORTABLY_LONG = 48 - - -def _safe_for_log_ticket(ticket): - """Creates a safe-for-printing-to-the-log ticket for a given ticket. - - Args: - ticket: Any links.Ticket. - - Returns: - A links.Ticket that is as much as can be equal to the given ticket but - possibly features values like the string "<payload of length 972321>" in - place of the actual values of the given ticket. - """ - if isinstance(ticket.payload, (basestring,)): - payload_length = len(ticket.payload) - else: - payload_length = -1 - if payload_length < _UNCOMFORTABLY_LONG: - return ticket - else: - return links.Ticket( - ticket.operation_id, ticket.sequence_number, - ticket.group, ticket.method, ticket.subscription, ticket.timeout, - ticket.allowance, ticket.initial_metadata, - '<payload of length {}>'.format(payload_length), - ticket.terminal_metadata, ticket.code, ticket.message, - ticket.termination, None) - - -class RecordingLink(links.Link): - """A Link that records every ticket passed to it.""" - - def __init__(self): - self._condition = threading.Condition() - self._tickets = [] - - def accept_ticket(self, ticket): - with self._condition: - self._tickets.append(ticket) - self._condition.notify_all() - - def join_link(self, link): - pass - - def block_until_tickets_satisfy(self, predicate): - """Blocks until the received tickets satisfy the given predicate. - - Args: - predicate: A callable that takes a sequence of tickets and returns a - boolean value. - """ - with self._condition: - while not predicate(self._tickets): - self._condition.wait() - - def tickets(self): - """Returns a copy of the list of all tickets received by this Link.""" - with self._condition: - return tuple(self._tickets) - - -class _Pipe(object): - """A conduit that logs all tickets passed through it.""" - - def __init__(self, name): - self._lock = threading.Lock() - self._name = name - self._left_mate = utilities.NULL_LINK - self._right_mate = utilities.NULL_LINK - - def accept_left_to_right_ticket(self, ticket): - with self._lock: - logging.warning( - '%s: moving left to right through %s: %s', time.time(), self._name, - _safe_for_log_ticket(ticket)) - try: - self._right_mate.accept_ticket(ticket) - except Exception as e: # pylint: disable=broad-except - logging.exception(e) - - def accept_right_to_left_ticket(self, ticket): - with self._lock: - logging.warning( - '%s: moving right to left through %s: %s', time.time(), self._name, - _safe_for_log_ticket(ticket)) - try: - self._left_mate.accept_ticket(ticket) - except Exception as e: # pylint: disable=broad-except - logging.exception(e) - - def join_left_mate(self, left_mate): - with self._lock: - self._left_mate = utilities.NULL_LINK if left_mate is None else left_mate - - def join_right_mate(self, right_mate): - with self._lock: - self._right_mate = ( - utilities.NULL_LINK if right_mate is None else right_mate) - - -class _Facade(links.Link): - - def __init__(self, accept, join): - self._accept = accept - self._join = join - - def accept_ticket(self, ticket): - self._accept(ticket) - - def join_link(self, link): - self._join(link) - - -def logging_links(name): - """Creates a conduit that logs all tickets passed through it. - - Args: - name: A name to use for the conduit to identify itself in logging output. - - Returns: - Two links.Links, the first of which is the "left" side of the conduit - and the second of which is the "right" side of the conduit. - """ - pipe = _Pipe(name) - left_facade = _Facade(pipe.accept_left_to_right_ticket, pipe.join_left_mate) - right_facade = _Facade(pipe.accept_right_to_left_ticket, pipe.join_right_mate) - return left_facade, right_facade |