blob: aec6fa8a12a47da6f5931f814df5834817cd38bc [file] [log] [blame]
// Copyright 2022 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 "pw_rpc/synchronous_call.h"
#include <chrono>
#include "gtest/gtest.h"
#include "pw_chrono/system_clock.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/packet.h"
#include "pw_rpc/nanopb/fake_channel_output.h"
#include "pw_rpc_test_protos/test.rpc.pb.h"
#include "pw_status/status.h"
#include "pw_status/status_with_size.h"
#include "pw_thread/thread.h"
#include "pw_work_queue/test_thread.h"
#include "pw_work_queue/work_queue.h"
namespace pw::rpc::test {
namespace {
using pw::rpc::test::pw_rpc::nanopb::TestService;
using MethodInfo = internal::MethodInfo<TestService::TestUnaryRpc>;
class SynchronousCallTest : public ::testing::Test {
public:
SynchronousCallTest()
: channels_({{Channel::Create<42>(&fake_output_)}}), client_(channels_) {}
void SetUp() override {
work_thread_ =
thread::Thread(work_queue::test::WorkQueueThreadOptions(), work_queue_);
}
void TearDown() override {
work_queue_.RequestStop();
work_thread_.join();
}
protected:
using FakeChannelOutput = NanopbFakeChannelOutput<2>;
void OnSend(span<const std::byte> buffer, Status status) {
if (!status.ok()) {
return;
}
auto result = internal::Packet::FromBuffer(buffer);
EXPECT_TRUE(result.ok());
request_packet_ = *result;
EXPECT_TRUE(work_queue_.PushWork([this]() { SendResponse(); }).ok());
}
void SendResponse() {
std::array<std::byte, 256> buffer;
std::array<std::byte, 32> payload_buffer;
StatusWithSize size_status =
MethodInfo::serde().EncodeResponse(&response_, payload_buffer);
EXPECT_TRUE(size_status.ok());
auto response =
internal::Packet::Response(request_packet_, response_status_);
response.set_payload({payload_buffer.data(), size_status.size()});
EXPECT_TRUE(client_.ProcessPacket(response.Encode(buffer).value()).ok());
}
void set_response(const pw_rpc_test_TestResponse& response,
Status response_status = OkStatus()) {
response_ = response;
response_status_ = response_status;
output().set_on_send([this](span<const std::byte> buffer, Status status) {
OnSend(buffer, status);
});
}
MethodInfo::GeneratedClient generated_client() {
return MethodInfo::GeneratedClient(client(), channel().id());
}
FakeChannelOutput& output() { return fake_output_; }
const Channel& channel() const { return channels_.front(); }
Client& client() { return client_; }
private:
FakeChannelOutput fake_output_;
std::array<Channel, 1> channels_;
Client client_;
thread::Thread work_thread_;
work_queue::WorkQueueWithBuffer<1> work_queue_;
pw_rpc_test_TestResponse response_{};
Status response_status_ = OkStatus();
internal::Packet request_packet_;
};
TEST_F(SynchronousCallTest, SynchronousCallSuccess) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
pw_rpc_test_TestResponse response{.value = 42, .repeated_field{}};
set_response(response, OkStatus());
auto result = SynchronousCall<TestService::TestUnaryRpc>(
client(), channel().id(), request);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.response().value, 42);
}
TEST_F(SynchronousCallTest, SynchronousCallServerError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
pw_rpc_test_TestResponse response{.value = 42, .repeated_field{}};
set_response(response, Status::Internal());
auto result = SynchronousCall<TestService::TestUnaryRpc>(
client(), channel().id(), request);
EXPECT_TRUE(result.is_error());
EXPECT_EQ(result.status(), Status::Internal());
// We should still receive the response
EXPECT_TRUE(result.is_server_response());
EXPECT_EQ(result.response().value, 42);
}
TEST_F(SynchronousCallTest, SynchronousCallRpcError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
// Internally, if Channel receives a non-ok status from the
// ChannelOutput::Send, it will always return Unknown.
output().set_send_status(Status::Unknown());
auto result = SynchronousCall<TestService::TestUnaryRpc>(
client(), channel().id(), request);
EXPECT_TRUE(result.is_rpc_error());
EXPECT_EQ(result.status(), Status::Unknown());
}
TEST_F(SynchronousCallTest, SynchronousCallForTimeoutError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
auto result = SynchronousCallFor<TestService::TestUnaryRpc>(
client(),
channel().id(),
request,
chrono::SystemClock::for_at_least(std::chrono::milliseconds(1)));
EXPECT_TRUE(result.is_timeout());
EXPECT_EQ(result.status(), Status::DeadlineExceeded());
}
TEST_F(SynchronousCallTest, SynchronousCallUntilTimeoutError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
auto result = SynchronousCallUntil<TestService::TestUnaryRpc>(
client(), channel().id(), request, chrono::SystemClock::now());
EXPECT_TRUE(result.is_timeout());
EXPECT_EQ(result.status(), Status::DeadlineExceeded());
}
TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallSuccess) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
pw_rpc_test_TestResponse response{.value = 42, .repeated_field{}};
set_response(response, OkStatus());
auto result =
SynchronousCall<TestService::TestUnaryRpc>(generated_client(), request);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.response().value, 42);
}
TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallServerError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
pw_rpc_test_TestResponse response{.value = 42, .repeated_field{}};
set_response(response, Status::Internal());
auto result =
SynchronousCall<TestService::TestUnaryRpc>(generated_client(), request);
EXPECT_TRUE(result.is_error());
EXPECT_EQ(result.status(), Status::Internal());
// We should still receive the response
EXPECT_TRUE(result.is_server_response());
EXPECT_EQ(result.response().value, 42);
}
TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallRpcError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
// Internally, if Channel receives a non-ok status from the
// ChannelOutput::Send, it will always return Unknown.
output().set_send_status(Status::Unknown());
auto result =
SynchronousCall<TestService::TestUnaryRpc>(generated_client(), request);
EXPECT_TRUE(result.is_rpc_error());
EXPECT_EQ(result.status(), Status::Unknown());
}
TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallForTimeoutError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
auto result = SynchronousCallFor<TestService::TestUnaryRpc>(
generated_client(),
request,
chrono::SystemClock::for_at_least(std::chrono::milliseconds(1)));
EXPECT_TRUE(result.is_timeout());
EXPECT_EQ(result.status(), Status::DeadlineExceeded());
}
TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallUntilTimeoutError) {
pw_rpc_test_TestRequest request{.integer = 5, .status_code = 0};
auto result = SynchronousCallUntil<TestService::TestUnaryRpc>(
generated_client(), request, chrono::SystemClock::now());
EXPECT_TRUE(result.is_timeout());
EXPECT_EQ(result.status(), Status::DeadlineExceeded());
}
} // namespace
} // namespace pw::rpc::test