blob: 6fcfd607ffe7a2ebf1dcc60d406a83bc6db0fe7e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Tests creating pw_rpc client."""
import unittest
from typing import Optional
from pw_protobuf_compiler import python_protos
from pw_status import Status
from pw_rpc import callback_client, client, packets
import pw_rpc.ids
from pw_rpc.internal.packet_pb2 import PacketType, RpcPacket
TEST_PROTO_1 = """\
syntax = "proto3";
package pw.test1;
message SomeMessage {
uint32 magic_number = 1;
}
message AnotherMessage {
enum Result {
FAILED = 0;
FAILED_MISERABLY = 1;
I_DONT_WANT_TO_TALK_ABOUT_IT = 2;
}
Result result = 1;
string payload = 2;
}
service PublicService {
rpc SomeUnary(SomeMessage) returns (AnotherMessage) {}
rpc SomeServerStreaming(SomeMessage) returns (stream AnotherMessage) {}
rpc SomeClientStreaming(stream SomeMessage) returns (AnotherMessage) {}
rpc SomeBidiStreaming(stream SomeMessage) returns (stream AnotherMessage) {}
}
"""
TEST_PROTO_2 = """\
syntax = "proto2";
package pw.test2;
message Request {
optional float magic_number = 1;
}
message Response {
}
service Alpha {
rpc Unary(Request) returns (Response) {}
}
service Bravo {
rpc BidiStreaming(stream Request) returns (stream Response) {}
}
"""
def _test_setup(output=None):
protos = python_protos.Library.from_strings([TEST_PROTO_1, TEST_PROTO_2])
return protos, client.Client.from_modules(
callback_client.Impl(),
[client.Channel(1, output),
client.Channel(2, lambda _: None)], protos.modules())
class ChannelClientTest(unittest.TestCase):
"""Tests the ChannelClient."""
def setUp(self) -> None:
self._channel_client = _test_setup()[1].channel(1)
def test_access_service_client_as_attribute_or_index(self) -> None:
self.assertIs(self._channel_client.rpcs.pw.test1.PublicService,
self._channel_client.rpcs['pw.test1.PublicService'])
self.assertIs(
self._channel_client.rpcs.pw.test1.PublicService,
self._channel_client.rpcs[pw_rpc.ids.calculate(
'pw.test1.PublicService')])
def test_access_method_client_as_attribute_or_index(self) -> None:
self.assertIs(self._channel_client.rpcs.pw.test2.Alpha.Unary,
self._channel_client.rpcs['pw.test2.Alpha']['Unary'])
self.assertIs(
self._channel_client.rpcs.pw.test2.Alpha.Unary,
self._channel_client.rpcs['pw.test2.Alpha'][pw_rpc.ids.calculate(
'Unary')])
def test_service_name(self) -> None:
self.assertEqual(
self._channel_client.rpcs.pw.test2.Alpha.Unary.service.name,
'Alpha')
self.assertEqual(
self._channel_client.rpcs.pw.test2.Alpha.Unary.service.full_name,
'pw.test2.Alpha')
def test_method_name(self) -> None:
self.assertEqual(
self._channel_client.rpcs.pw.test2.Alpha.Unary.method.name,
'Unary')
self.assertEqual(
self._channel_client.rpcs.pw.test2.Alpha.Unary.method.full_name,
'pw.test2.Alpha.Unary')
def test_iterate_over_all_methods(self) -> None:
channel_client = self._channel_client
all_methods = {
channel_client.rpcs.pw.test1.PublicService.SomeUnary,
channel_client.rpcs.pw.test1.PublicService.SomeServerStreaming,
channel_client.rpcs.pw.test1.PublicService.SomeClientStreaming,
channel_client.rpcs.pw.test1.PublicService.SomeBidiStreaming,
channel_client.rpcs.pw.test2.Alpha.Unary,
channel_client.rpcs.pw.test2.Bravo.BidiStreaming,
}
self.assertEqual(set(channel_client.methods()), all_methods)
def test_check_for_presence_of_services(self) -> None:
self.assertIn('pw.test1.PublicService', self._channel_client.rpcs)
self.assertIn(pw_rpc.ids.calculate('pw.test1.PublicService'),
self._channel_client.rpcs)
def test_check_for_presence_of_missing_services(self) -> None:
self.assertNotIn('PublicService', self._channel_client.rpcs)
self.assertNotIn('NotAService', self._channel_client.rpcs)
self.assertNotIn(-1213, self._channel_client.rpcs)
def test_check_for_presence_of_methods(self) -> None:
service = self._channel_client.rpcs.pw.test1.PublicService
self.assertIn('SomeUnary', service)
self.assertIn(pw_rpc.ids.calculate('SomeUnary'), service)
def test_check_for_presence_of_missing_methods(self) -> None:
service = self._channel_client.rpcs.pw.test1.PublicService
self.assertNotIn('Some', service)
self.assertNotIn('Unary', service)
self.assertNotIn(12345, service)
def test_method_fully_qualified_name(self) -> None:
self.assertIs(self._channel_client.method('pw.test2.Alpha/Unary'),
self._channel_client.rpcs.pw.test2.Alpha.Unary)
self.assertIs(self._channel_client.method('pw.test2.Alpha.Unary'),
self._channel_client.rpcs.pw.test2.Alpha.Unary)
class ClientTest(unittest.TestCase):
"""Tests the pw_rpc Client independently of the ClientImpl."""
def setUp(self) -> None:
self._last_packet_sent_bytes: Optional[bytes] = None
self._protos, self._client = _test_setup(self._save_packet)
def _save_packet(self, packet) -> None:
self._last_packet_sent_bytes = packet
def _last_packet_sent(self) -> RpcPacket:
packet = RpcPacket()
assert self._last_packet_sent_bytes is not None
packet.MergeFromString(self._last_packet_sent_bytes)
return packet
def test_channel(self) -> None:
self.assertEqual(self._client.channel(1).channel.id, 1)
self.assertEqual(self._client.channel(2).channel.id, 2)
def test_channel_default_is_first_listed(self) -> None:
self.assertEqual(self._client.channel().channel.id, 1)
def test_channel_invalid(self) -> None:
with self.assertRaises(KeyError):
self._client.channel(404)
def test_all_methods(self) -> None:
services = self._client.services
all_methods = {
services['pw.test1.PublicService'].methods['SomeUnary'],
services['pw.test1.PublicService'].methods['SomeServerStreaming'],
services['pw.test1.PublicService'].methods['SomeClientStreaming'],
services['pw.test1.PublicService'].methods['SomeBidiStreaming'],
services['pw.test2.Alpha'].methods['Unary'],
services['pw.test2.Bravo'].methods['BidiStreaming'],
}
self.assertEqual(set(self._client.methods()), all_methods)
def test_method_present(self) -> None:
self.assertIs(
self._client.method('pw.test1.PublicService.SomeUnary'), self.
_client.services['pw.test1.PublicService'].methods['SomeUnary'])
self.assertIs(
self._client.method('pw.test1.PublicService/SomeUnary'), self.
_client.services['pw.test1.PublicService'].methods['SomeUnary'])
def test_method_invalid_format(self) -> None:
with self.assertRaises(ValueError):
self._client.method('SomeUnary')
def test_method_not_present(self) -> None:
with self.assertRaises(KeyError):
self._client.method('pw.test1.PublicService/ThisIsNotGood')
with self.assertRaises(KeyError):
self._client.method('nothing.Good')
def test_process_packet_invalid_proto_data(self) -> None:
self.assertIs(self._client.process_packet(b'NOT a packet!'),
Status.DATA_LOSS)
def test_process_packet_not_for_client(self) -> None:
self.assertIs(
self._client.process_packet(
RpcPacket(type=PacketType.REQUEST).SerializeToString()),
Status.INVALID_ARGUMENT)
def test_process_packet_unrecognized_channel(self) -> None:
self.assertIs(
self._client.process_packet(
packets.encode_response(
(123, 456, 789),
self._protos.packages.pw.test2.Request())),
Status.NOT_FOUND)
def test_process_packet_unrecognized_service(self) -> None:
self.assertIs(
self._client.process_packet(
packets.encode_response(
(1, 456, 789), self._protos.packages.pw.test2.Request())),
Status.OK)
self.assertEqual(
self._last_packet_sent(),
RpcPacket(type=PacketType.CLIENT_ERROR,
channel_id=1,
service_id=456,
method_id=789,
status=Status.NOT_FOUND.value))
def test_process_packet_unrecognized_method(self) -> None:
service = next(iter(self._client.services))
self.assertIs(
self._client.process_packet(
packets.encode_response(
(1, service.id, 789),
self._protos.packages.pw.test2.Request())), Status.OK)
self.assertEqual(
self._last_packet_sent(),
RpcPacket(type=PacketType.CLIENT_ERROR,
channel_id=1,
service_id=service.id,
method_id=789,
status=Status.NOT_FOUND.value))
def test_process_packet_non_pending_method(self) -> None:
service = next(iter(self._client.services))
method = next(iter(service.methods))
self.assertIs(
self._client.process_packet(
packets.encode_response(
(1, service.id, method.id),
self._protos.packages.pw.test2.Request())), Status.OK)
self.assertEqual(
self._last_packet_sent(),
RpcPacket(type=PacketType.CLIENT_ERROR,
channel_id=1,
service_id=service.id,
method_id=method.id,
status=Status.FAILED_PRECONDITION.value))
if __name__ == '__main__':
unittest.main()