Index: third_party/grpc/src/python/grpcio/tests/unit/framework/interfaces/base/_control.py |
diff --git a/third_party/grpc/src/python/grpcio/tests/unit/framework/interfaces/base/_control.py b/third_party/grpc/src/python/grpcio/tests/unit/framework/interfaces/base/_control.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..38102b198a70fc3e2ef59c627f03e8fe5e6a2052 |
--- /dev/null |
+++ b/third_party/grpc/src/python/grpcio/tests/unit/framework/interfaces/base/_control.py |
@@ -0,0 +1,568 @@ |
+# 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 abc |
+import collections |
+import enum |
+import random # pylint: disable=unused-import |
+import threading |
+import time |
+ |
+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 = '\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(object): |
+ __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(object): |
+ __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) |