mirror of https://github.com/grpc/grpc.git
Merge pull request #2963 from nathanielmanistaatgoogle/base-test-suite
A test suite for the RPC Framework base interfacepull/2970/head
commit
3447c08ed1
7 changed files with 1277 additions and 0 deletions
@ -0,0 +1,30 @@ |
||||
# 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. |
||||
|
||||
|
@ -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 grpc_test.framework.common import test_constants |
||||
from grpc_test.framework.interfaces.base import _sequence |
||||
from grpc_test.framework.interfaces.base import _state |
||||
from grpc_test.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', |
||||
'conclude_service_outcome',))): |
||||
"""""" |
||||
|
||||
@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 is not self._sequence.outcome.service: |
||||
self._failed( |
||||
'Incorrect service-side outcome: %s should have been %s' % ( |
||||
outcome, self._sequence.outcome.service)) |
||||
elif self._state.invocation_side_outcome is not None: |
||||
self._passed(self._state.invocation_side_outcome, outcome) |
||||
|
||||
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 is not self._sequence.outcome.invocation: |
||||
self._failed( |
||||
'Incorrect invocation-side outcome: %s should have been %s' % ( |
||||
outcome, self._sequence.outcome.invocation)) |
||||
elif self._state.service_side_outcome is not None: |
||||
self._passed(outcome, self._state.service_side_outcome) |
||||
|
||||
|
||||
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) |
@ -0,0 +1,168 @@ |
||||
# 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 grpc_test.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 Outcome(collections.namedtuple('Outcome', ('invocation', 'service',))): |
||||
"""A description of the expected outcome of an operation test. |
||||
|
||||
Attributes: |
||||
invocation: The base.Outcome value expected on the invocation side of the |
||||
operation. |
||||
service: The base.Outcome value expected on the service side of the |
||||
operation. |
||||
""" |
||||
|
||||
|
||||
class Sequence( |
||||
collections.namedtuple( |
||||
'Sequence', |
||||
('name', 'maximum_duration', 'invocation', 'elements', 'outcome',))): |
||||
"""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: An Outcome value describing the expected outcome 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)), |
||||
), |
||||
Outcome(base.Outcome.COMPLETED, base.Outcome.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)), |
||||
), |
||||
Outcome(base.Outcome.COMPLETED, base.Outcome.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, |
||||
) |
@ -0,0 +1,55 @@ |
||||
# 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 |
@ -0,0 +1,260 @@ |
||||
# 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.""" |
||||
|
||||
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 grpc_test.framework.common import test_constants |
||||
from grpc_test.framework.interfaces.base import _control |
||||
from grpc_test.framework.interfaces.base import test_interfaces |
||||
|
||||
_SYNCHRONICITY_VARIATION = (('Sync', False), ('Async', True)) |
||||
|
||||
_EMPTY_OUTCOME_DICT = {outcome: 0 for outcome in base.Outcome} |
||||
|
||||
|
||||
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 _Servicer(base.Servicer): |
||||
"""An 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() |
||||
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) |
||||
|
||||
|
||||
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) |
||||
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_end.stop_gracefully() |
||||
service_end.stop_gracefully() |
||||
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_DICT) |
||||
expected_invocation_stats[instruction.conclude_invocation_outcome] += 1 |
||||
self.assertDictEqual(expected_invocation_stats, invocation_stats) |
||||
expected_service_stats = dict(_EMPTY_OUTCOME_DICT) |
||||
expected_service_stats[instruction.conclude_service_outcome] += 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, |
||||
})) |
||||
|
||||
return test_case_classes |
@ -0,0 +1,186 @@ |
||||
# 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 |
||||
|
||||
from grpc.framework.interfaces.base import base # pylint: disable=unused-import |
||||
|
||||
|
||||
class Serialization(object): |
||||
"""Specifies serialization and deserialization of test payloads.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
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(object): |
||||
"""Specifies an implementation of the Base layer.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@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): |
||||
"""Provices 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() |
Loading…
Reference in new issue