| // Copyright 2021 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. |
| |
| #include "gtest/gtest.h" |
| #include "pw_rpc/internal/hash.h" |
| #include "pw_rpc/nanopb_test_method_context.h" |
| #include "pw_rpc_nanopb_private/internal_test_utils.h" |
| #include "pw_rpc_private/internal_test_utils.h" |
| #include "pw_rpc_test_protos/test.rpc.pb.h" |
| |
| namespace pw::rpc { |
| namespace test { |
| |
| class TestService final : public generated::TestService<TestService> { |
| public: |
| Status TestRpc(ServerContext&, |
| const pw_rpc_test_TestRequest& request, |
| pw_rpc_test_TestResponse& response) { |
| response.value = request.integer + 1; |
| return static_cast<Status::Code>(request.status_code); |
| } |
| |
| static void TestStreamRpc( |
| ServerContext&, |
| const pw_rpc_test_TestRequest& request, |
| ServerWriter<pw_rpc_test_TestStreamResponse>& writer) { |
| for (int i = 0; i < request.integer; ++i) { |
| writer.Write({.chunk = {}, .number = static_cast<uint32_t>(i)}); |
| } |
| |
| writer.Finish(static_cast<Status::Code>(request.status_code)); |
| } |
| }; |
| |
| } // namespace test |
| |
| namespace { |
| |
| TEST(NanopbCodegen, CompilesProperly) { |
| test::TestService service; |
| EXPECT_EQ(service.id(), internal::Hash("pw.rpc.test.TestService")); |
| EXPECT_STREQ(service.name(), "TestService"); |
| } |
| |
| TEST(NanopbCodegen, Server_InvokeUnaryRpc) { |
| PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestRpc) context; |
| |
| EXPECT_EQ(OkStatus(), |
| context.call({.integer = 123, .status_code = OkStatus().code()})); |
| |
| EXPECT_EQ(124, context.response().value); |
| |
| EXPECT_EQ(Status::InvalidArgument(), |
| context.call({.integer = 999, |
| .status_code = Status::InvalidArgument().code()})); |
| EXPECT_EQ(1000, context.response().value); |
| } |
| |
| TEST(NanopbCodegen, Server_InvokeStreamingRpc) { |
| PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc) context; |
| |
| context.call({.integer = 0, .status_code = Status::Aborted().code()}); |
| |
| EXPECT_EQ(Status::Aborted(), context.status()); |
| EXPECT_TRUE(context.done()); |
| EXPECT_TRUE(context.responses().empty()); |
| EXPECT_EQ(0u, context.total_responses()); |
| |
| context.call({.integer = 4, .status_code = OkStatus().code()}); |
| |
| ASSERT_EQ(4u, context.responses().size()); |
| ASSERT_EQ(4u, context.total_responses()); |
| |
| for (size_t i = 0; i < context.responses().size(); ++i) { |
| EXPECT_EQ(context.responses()[i].number, i); |
| } |
| |
| EXPECT_EQ(OkStatus().code(), context.status()); |
| } |
| |
| TEST(NanopbCodegen, |
| Server_InvokeStreamingRpc_ContextKeepsFixedNumberOfResponses) { |
| PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc, 3) context; |
| |
| ASSERT_EQ(3u, context.responses().max_size()); |
| |
| context.call({.integer = 5, .status_code = Status::NotFound().code()}); |
| |
| ASSERT_EQ(3u, context.responses().size()); |
| ASSERT_EQ(5u, context.total_responses()); |
| |
| EXPECT_EQ(context.responses()[0].number, 0u); |
| EXPECT_EQ(context.responses()[1].number, 1u); |
| EXPECT_EQ(context.responses()[2].number, 4u); |
| } |
| |
| TEST(NanopbCodegen, Server_InvokeStreamingRpc_ManualWriting) { |
| PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc, 3) context; |
| |
| ASSERT_EQ(3u, context.responses().max_size()); |
| |
| auto writer = context.writer(); |
| |
| writer.Write({.chunk = {}, .number = 3}); |
| writer.Write({.chunk = {}, .number = 6}); |
| writer.Write({.chunk = {}, .number = 9}); |
| |
| EXPECT_FALSE(context.done()); |
| |
| writer.Finish(Status::Cancelled()); |
| ASSERT_TRUE(context.done()); |
| EXPECT_EQ(Status::Cancelled(), context.status()); |
| |
| ASSERT_EQ(3u, context.responses().size()); |
| ASSERT_EQ(3u, context.total_responses()); |
| |
| EXPECT_EQ(context.responses()[0].number, 3u); |
| EXPECT_EQ(context.responses()[1].number, 6u); |
| EXPECT_EQ(context.responses()[2].number, 9u); |
| } |
| |
| using TestServiceClient = test::nanopb::TestServiceClient; |
| |
| TEST(NanopbCodegen, Client_GeneratesCallAliases) { |
| static_assert( |
| std::is_same_v<TestServiceClient::TestRpcCall, |
| NanopbClientCall< |
| internal::UnaryCallbacks<pw_rpc_test_TestResponse>>>); |
| static_assert( |
| std::is_same_v<TestServiceClient::TestStreamRpcCall, |
| NanopbClientCall<internal::ServerStreamingCallbacks< |
| pw_rpc_test_TestStreamResponse>>>); |
| } |
| |
| TEST(NanopbCodegen, ClientCall_DefaultConstructor) { |
| TestServiceClient::TestRpcCall unary_call; |
| TestServiceClient::TestStreamRpcCall server_streaming_call; |
| } |
| |
| TEST(NanopbCodegen, Client_InvokesUnaryRpcWithCallback) { |
| constexpr uint32_t service_id = internal::Hash("pw.rpc.test.TestService"); |
| constexpr uint32_t method_id = internal::Hash("TestRpc"); |
| |
| ClientContextForTest<128, 128, 99, service_id, method_id> context; |
| |
| struct { |
| Status last_status = Status::Unknown(); |
| int response_value = -1; |
| } result; |
| |
| auto call = TestServiceClient::TestRpc( |
| context.channel(), |
| {.integer = 123, .status_code = 0}, |
| [&result](const pw_rpc_test_TestResponse& response, Status status) { |
| result.last_status = status; |
| result.response_value = response.value; |
| }); |
| |
| EXPECT_EQ(context.output().packet_count(), 1u); |
| auto packet = context.output().sent_packet(); |
| EXPECT_EQ(packet.channel_id(), context.channel().id()); |
| EXPECT_EQ(packet.service_id(), service_id); |
| EXPECT_EQ(packet.method_id(), method_id); |
| PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload()); |
| EXPECT_EQ(sent_proto.integer, 123); |
| |
| PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42); |
| context.SendResponse(OkStatus(), response); |
| EXPECT_EQ(result.last_status, OkStatus()); |
| EXPECT_EQ(result.response_value, 42); |
| } |
| |
| TEST(NanopbCodegen, Client_InvokesServerStreamingRpcWithCallback) { |
| constexpr uint32_t service_id = internal::Hash("pw.rpc.test.TestService"); |
| constexpr uint32_t method_id = internal::Hash("TestStreamRpc"); |
| |
| ClientContextForTest<128, 128, 99, service_id, method_id> context; |
| |
| struct { |
| bool active = true; |
| Status stream_status = Status::Unknown(); |
| int response_value = -1; |
| } result; |
| |
| auto call = TestServiceClient::TestStreamRpc( |
| context.channel(), |
| {.integer = 123, .status_code = 0}, |
| [&result](const pw_rpc_test_TestStreamResponse& response) { |
| result.active = true; |
| result.response_value = response.number; |
| }, |
| [&result](Status status) { |
| result.active = false; |
| result.stream_status = status; |
| }); |
| |
| EXPECT_EQ(context.output().packet_count(), 1u); |
| auto packet = context.output().sent_packet(); |
| EXPECT_EQ(packet.channel_id(), context.channel().id()); |
| EXPECT_EQ(packet.service_id(), service_id); |
| EXPECT_EQ(packet.method_id(), method_id); |
| PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload()); |
| EXPECT_EQ(sent_proto.integer, 123); |
| |
| PW_ENCODE_PB( |
| pw_rpc_test_TestStreamResponse, response, .chunk = {}, .number = 11u); |
| context.SendServerStream(response); |
| EXPECT_TRUE(result.active); |
| EXPECT_EQ(result.response_value, 11); |
| |
| context.SendResponse(Status::NotFound()); |
| EXPECT_FALSE(result.active); |
| EXPECT_EQ(result.stream_status, Status::NotFound()); |
| } |
| |
| } // namespace |
| } // namespace pw::rpc |