blob: d5d1e7ebcca1f9aae63ce0ee8211cfb4cfe7bc67 [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/test_helpers.h"
#include <mutex>
#include "gtest/gtest.h"
#include "pw_chrono/system_clock.h"
#include "pw_containers/vector.h"
#include "pw_result/result.h"
#include "pw_rpc/echo.pwpb.h"
#include "pw_rpc/echo.rpc.pwpb.h"
#include "pw_rpc/pwpb/client_testing.h"
#include "pw_rpc/pwpb/server_reader_writer.h"
#include "pw_status/status.h"
#include "pw_sync/interrupt_spin_lock.h"
#include "pw_sync/lock_annotations.h"
#include "pw_sync/timed_thread_notification.h"
namespace pw::rpc::test {
namespace {
using namespace std::chrono_literals;
constexpr auto kWaitForEchoTimeout =
pw::chrono::SystemClock::for_at_least(100ms);
// Class that we want to test.
//
// It's main purpose is to ask EchoService for Echo and provide its result
// through WaitForEcho/LastEcho pair to the user.
class EntityUnderTest {
public:
explicit EntityUnderTest(pw_rpc::pwpb::EchoService::Client& echo_client)
: echo_client_(echo_client) {}
void AskForEcho() {
call_ = echo_client_.Echo(
EchoMessage::Message{},
[this](const EchoMessage::Message& response, pw::Status status) {
lock_.lock();
if (status.ok()) {
last_echo_ = response.msg;
} else {
last_echo_ = status;
}
lock_.unlock();
notifier_.release();
},
[this](pw::Status status) {
lock_.lock();
last_echo_ = status;
lock_.unlock();
notifier_.release();
});
}
bool WaitForEcho(pw::chrono::SystemClock::duration duration) {
return notifier_.try_acquire_for(duration);
}
pw::Result<pw::Vector<char, 64>> LastEcho() const {
std::lock_guard<pw::sync::InterruptSpinLock> lock(lock_);
return last_echo_;
}
private:
pw_rpc::pwpb::EchoService::Client& echo_client_;
PwpbUnaryReceiver<EchoMessage::Message> call_;
pw::sync::TimedThreadNotification notifier_;
pw::Result<pw::Vector<char, 64>> last_echo_ PW_GUARDED_BY(lock_);
mutable pw::sync::InterruptSpinLock lock_;
};
TEST(RpcTestHelpersTest, SendResponseIfCalledOk) {
PwpbClientTestContext client_context;
pw_rpc::pwpb::EchoService::Client client(client_context.client(),
client_context.channel().id());
EntityUnderTest entity(client);
// We need to call the function that will initiate the request before we can
// send the response back.
entity.AskForEcho();
// SendResponseIfCalled blocks until request is received by the service (it is
// sent by AskForEcho to EchoService in this case) and responds to it with the
// response.
//
// SendResponseIfCalled will timeout if no request were sent in the `timeout`
// interval (see SendResponseIfCalledWithoutRequest test for the example).
ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
client_context, {.msg = "Hello"}),
OkStatus());
// After SendResponseIfCalled returned OkStatus client should have received
// the response back in the RPC thread, so we can check it here. Because it is
// a separate thread we still need to wait with the timeout.
ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
pw::Result<pw::Vector<char, 64>> result = entity.LastEcho();
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.value(), (pw::Vector<char, 64>{"Hello"}));
}
TEST(RpcTestHelpersTest, SendResponseIfCalledNotOk) {
PwpbClientTestContext client_context;
pw_rpc::pwpb::EchoService::Client client(client_context.client(),
client_context.channel().id());
EntityUnderTest entity(client);
// We need to call the function that will initiate the request before we can
// send the response back.
entity.AskForEcho();
// SendResponseIfCalled also can be used to respond with failures. In this
// case we are sending back pw::Status::InvalidArgument and expect to see it
// on the client side.
//
// SendResponseIfCalled result status is not the same status as it sends to
// the client, so we still are expecting the OkStatus here.
ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
client_context, {}, pw::Status::InvalidArgument()),
OkStatus());
// After SendResponseIfCalled returned OkStatus client should have received
// the response back in the RPC thread, so we can check it here. Because it is
// a separate thread we still need to wait with the timeout.
ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
EXPECT_EQ(entity.LastEcho().status(), Status::InvalidArgument());
}
TEST(RpcTestHelpersTest, SendResponseIfCalledNotOkShortcut) {
PwpbClientTestContext client_context;
pw_rpc::pwpb::EchoService::Client client(client_context.client(),
client_context.channel().id());
EntityUnderTest entity(client);
// We need to call the function that will initiate the request before we can
// send the response back.
entity.AskForEcho();
// SendResponseIfCalled shortcut version exists to respond with failures. It
// works exactly the same, but doesn't have the response argument. In this
// case we are sending back pw::Status::InvalidArgument and expect to see it
// on the client side.
//
// SendResponseIfCalled result status is not the same status as it sends to
// the client, so we still are expecting the OkStatus here.
ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
client_context, pw::Status::InvalidArgument()),
OkStatus());
// After SendResponseIfCalled returned OkStatus client should have received
// the response back in the RPC thread, so we can check it here. Because it is
// a separate thread we still need to wait with the timeout.
ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
EXPECT_EQ(entity.LastEcho().status(), Status::InvalidArgument());
}
TEST(RpcTestHelpersTest, SendResponseIfCalledWithoutRequest) {
PwpbClientTestContext client_context;
pw_rpc::pwpb::EchoService::Client client(client_context.client(),
client_context.channel().id());
// We don't send any request in this test and SendResponseIfCalled is expected
// to fail on waiting for the request with pw::Status::FailedPrecondition.
const auto start_time = pw::chrono::SystemClock::now();
auto status = SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
client_context,
{.msg = "Hello"},
pw::OkStatus(),
/*timeout=*/pw::chrono::SystemClock::for_at_least(10ms));
// We set our timeout for SendResponseIfCalled to 10ms, so it should be at
// least 10ms since we called the SendResponseIfCalled.
EXPECT_GE(pw::chrono::SystemClock::now() - start_time,
pw::chrono::SystemClock::for_at_least(10ms));
// We expect SendResponseIfCalled to fail, because there were no request sent
// for the given method.
EXPECT_EQ(status, Status::DeadlineExceeded());
}
} // namespace
} // namespace pw::rpc::test