[ntcore] Translate unit tests to catch2 (#8997)

Use manual mocks instead of googlemock.
This commit is contained in:
Peter Johnson
2026-07-02 15:26:32 -07:00
committed by GitHub
parent 023b70268a
commit 633ca1152e
34 changed files with 3083 additions and 2103 deletions

View File

@@ -190,7 +190,7 @@ cc_test(
],
deps = [
":ntcore",
"//thirdparty/googletest",
"//thirdparty/catch2",
"//wpiutil:wpiutil-testlib",
],
)

View File

@@ -44,7 +44,7 @@ wpilib_target_warnings(ntcoredev)
target_link_libraries(ntcoredev ntcore)
if(WPILIB_WITH_TESTS)
wpilib_add_test(ntcore src/test/native/cpp)
wpilib_add_test_catch2(ntcore src/test/native/cpp)
target_include_directories(ntcore_test PRIVATE src/main/native/cpp)
target_link_libraries(ntcore_test ntcore googletest wpiutil_testlib)
target_link_libraries(ntcore_test ntcore wpiutil_testlib)
endif()

View File

@@ -11,6 +11,7 @@ ext {
nativeName = 'ntcore'
devMain = 'org.wpilib.networktables.DevMain'
nativeTestSuiteName = "${nativeName}Catch2Test"
generatedSources = "$projectDir/src/generated/main/native/cpp"
generatedHeaders = "$projectDir/src/generated/main/native/include"
jniSplitSetup = {

View File

@@ -6,20 +6,21 @@
#include <thread>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include "TestPrinters.hpp"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/Synchronization.hpp"
#include "wpi/util/mutex.hpp"
class ConnectionListenerTest : public ::testing::Test {
class ConnectionListenerTest {
public:
ConnectionListenerTest()
: server_inst(wpi::nt::CreateInstance()),
client_inst(wpi::nt::CreateInstance()) {}
~ConnectionListenerTest() override {
~ConnectionListenerTest() {
wpi::nt::DestroyInstance(server_inst);
wpi::nt::DestroyInstance(client_inst);
}
@@ -42,32 +43,33 @@ void ConnectionListenerTest::Connect(const char* address, unsigned int port4) {
while (!wpi::nt::IsConnected(client_inst)) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (++count > 30) {
FAIL() << "timed out waiting for client to start";
FAIL("timed out waiting for client to start");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
TEST_F(ConnectionListenerTest, Polled) {
TEST_CASE_METHOD(ConnectionListenerTest, "ConnectionListenerTest Polled",
"[ntcore][connection-listener]") {
// set up the poller
NT_ListenerPoller poller = wpi::nt::CreateListenerPoller(server_inst);
ASSERT_NE(poller, 0);
REQUIRE(poller != 0);
NT_Listener handle = wpi::nt::AddPolledListener(
poller, server_inst, wpi::nt::EventFlags::CONNECTION);
ASSERT_NE(handle, 0);
REQUIRE(handle != 0);
// trigger a connect event
Connect("127.0.0.1", 10020);
// get the event
bool timed_out = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timed_out));
ASSERT_FALSE(timed_out);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timed_out));
REQUIRE_FALSE(timed_out);
auto result = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(handle, result[0].listener);
EXPECT_TRUE(result[0].GetConnectionInfo());
EXPECT_EQ(result[0].flags, wpi::nt::EventFlags::CONNECTED);
REQUIRE(result.size() == 1u);
CHECK(handle == result[0].listener);
CHECK(result[0].GetConnectionInfo());
CHECK(result[0].flags == wpi::nt::EventFlags::CONNECTED);
// trigger a disconnect event
wpi::nt::StopClient(client_inst);
@@ -75,20 +77,19 @@ TEST_F(ConnectionListenerTest, Polled) {
// get the event
timed_out = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timed_out));
ASSERT_FALSE(timed_out);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timed_out));
REQUIRE_FALSE(timed_out);
result = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(handle, result[0].listener);
EXPECT_TRUE(result[0].GetConnectionInfo());
EXPECT_EQ(result[0].flags, wpi::nt::EventFlags::DISCONNECTED);
REQUIRE(result.size() == 1u);
CHECK(handle == result[0].listener);
CHECK(result[0].GetConnectionInfo());
CHECK(result[0].flags == wpi::nt::EventFlags::DISCONNECTED);
}
class ConnectionListenerVariantTest
: public ConnectionListenerTest,
public ::testing::WithParamInterface<std::pair<const char*, int>> {};
TEST_P(ConnectionListenerVariantTest, Threaded) {
TEST_CASE_METHOD(ConnectionListenerTest, "ConnectionListenerTest Threaded",
"[ntcore][connection-listener]") {
auto param = GENERATE(std::pair{"127.0.0.1", 0}, std::pair{"127.0.0.1 ", 1},
std::pair{" 127.0.0.1 ", 2});
wpi::util::mutex m;
std::vector<wpi::nt::Event> result;
auto handle = wpi::nt::AddListener(
@@ -98,19 +99,19 @@ TEST_P(ConnectionListenerVariantTest, Threaded) {
});
// trigger a connect event
Connect(GetParam().first, 20001 + GetParam().second);
Connect(param.first, 20001 + param.second);
bool timed_out = false;
ASSERT_TRUE(wpi::util::WaitForObject(handle, 1.0, &timed_out));
ASSERT_FALSE(timed_out);
REQUIRE(wpi::util::WaitForObject(handle, 1.0, &timed_out));
REQUIRE_FALSE(timed_out);
// get the event
{
std::scoped_lock lock{m};
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(handle, result[0].listener);
EXPECT_TRUE(result[0].GetConnectionInfo());
EXPECT_EQ(result[0].flags, wpi::nt::EventFlags::CONNECTED);
REQUIRE(result.size() == 1u);
CHECK(handle == result[0].listener);
CHECK(result[0].GetConnectionInfo());
CHECK(result[0].flags == wpi::nt::EventFlags::CONNECTED);
result.clear();
}
@@ -124,15 +125,9 @@ TEST_P(ConnectionListenerVariantTest, Threaded) {
// get the event
{
std::scoped_lock lock{m};
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(handle, result[0].listener);
EXPECT_TRUE(result[0].GetConnectionInfo());
EXPECT_EQ(result[0].flags, wpi::nt::EventFlags::DISCONNECTED);
REQUIRE(result.size() == 1u);
CHECK(handle == result[0].listener);
CHECK(result[0].GetConnectionInfo());
CHECK(result[0].flags == wpi::nt::EventFlags::DISCONNECTED);
}
}
INSTANTIATE_TEST_SUITE_P(ConnectionListenerVariantTests,
ConnectionListenerVariantTest,
testing::Values(std::pair{"127.0.0.1", 0},
std::pair{"127.0.0.1 ", 1},
std::pair{" 127.0.0.1 ", 2}));

File diff suppressed because it is too large Load Diff

View File

@@ -4,18 +4,18 @@
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "Handle.hpp"
#include "TestPrinters.hpp"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/Synchronization.h"
class LoggerTest : public ::testing::Test {
class LoggerTest {
public:
LoggerTest() : m_inst(wpi::nt::CreateInstance()) {}
~LoggerTest() override { wpi::nt::DestroyInstance(m_inst); }
~LoggerTest() { wpi::nt::DestroyInstance(m_inst); }
void Generate();
void Check(const std::vector<wpi::nt::Event>& events, NT_Listener handle,
@@ -38,27 +38,27 @@ void LoggerTest::Generate() {
void LoggerTest::Check(const std::vector<wpi::nt::Event>& events,
NT_Listener handle, bool infoMsg, bool errMsg) {
size_t count = (infoMsg ? 1u : 0u) + (errMsg ? 1u : 0u);
ASSERT_EQ(events.size(), count);
REQUIRE(events.size() == count);
for (size_t i = 0; i < count; ++i) {
ASSERT_EQ(events[i].listener, handle);
ASSERT_EQ(events[i].flags & wpi::nt::EventFlags::LOG_MESSAGE,
wpi::nt::EventFlags::LOG_MESSAGE);
REQUIRE(events[i].listener == handle);
REQUIRE((events[i].flags & wpi::nt::EventFlags::LOG_MESSAGE) ==
wpi::nt::EventFlags::LOG_MESSAGE);
auto log = events[i].GetLogMessage();
ASSERT_TRUE(log);
REQUIRE(log);
if (infoMsg) {
ASSERT_EQ(log->message, "starting network client");
ASSERT_EQ(log->level, NT_LOG_INFO);
REQUIRE(log->message == "starting network client");
REQUIRE(log->level == NT_LOG_INFO);
infoMsg = false;
} else if (errMsg) {
ASSERT_EQ(log->message,
"trying to publish invalid topic handle (386924549)");
ASSERT_EQ(log->level, NT_LOG_ERROR);
REQUIRE(log->message ==
"trying to publish invalid topic handle (386924549)");
REQUIRE(log->level == NT_LOG_ERROR);
errMsg = false;
}
}
}
TEST_F(LoggerTest, DefaultLogRange) {
TEST_CASE_METHOD(LoggerTest, "LoggerTest DefaultLogRange", "[ntcore][logger]") {
auto poller = wpi::nt::CreateListenerPoller(m_inst);
auto handle = wpi::nt::AddPolledListener(poller, m_inst,
wpi::nt::EventFlags::LOG_MESSAGE);
@@ -66,33 +66,33 @@ TEST_F(LoggerTest, DefaultLogRange) {
Generate();
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
Check(events, handle, true, true);
}
TEST_F(LoggerTest, InfoOnly) {
TEST_CASE_METHOD(LoggerTest, "LoggerTest InfoOnly", "[ntcore][logger]") {
auto poller = wpi::nt::CreateListenerPoller(m_inst);
auto handle = wpi::nt::AddPolledLogger(poller, NT_LOG_INFO, NT_LOG_INFO);
Generate();
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
Check(events, handle, true, false);
}
TEST_F(LoggerTest, Error) {
TEST_CASE_METHOD(LoggerTest, "LoggerTest Error", "[ntcore][logger]") {
auto poller = wpi::nt::CreateListenerPoller(m_inst);
auto handle = wpi::nt::AddPolledLogger(poller, NT_LOG_ERROR, 100);
Generate();
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
Check(events, handle, false, true);

View File

@@ -0,0 +1,191 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <string_view>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include "MockListenerStorage.hpp"
#include "PubSubOptionsMatcher.hpp"
#include "TestPrinters.hpp"
#include "net/MockMessageHandler.hpp"
#include "wpi/nt/ntcore_c.h"
#include "wpi/util/json.hpp"
namespace wpi::nt {
inline void CheckPublish(const net::MockClientMessageHandler::PublishCall& call,
std::string_view name, std::string_view typeStr,
const wpi::util::json& properties,
const PubSubOptionsImpl& options = {}) {
CHECK(call.name == name);
CHECK(call.typeStr == typeStr);
CHECK(call.properties == properties);
CHECK(call.options == options);
}
inline void CheckPublish(const net::MockClientMessageHandler::PublishCall& call,
int pubuid, std::string_view name,
std::string_view typeStr,
const wpi::util::json& properties,
const PubSubOptionsImpl& options = {}) {
CHECK(call.pubuid == pubuid);
CheckPublish(call, name, typeStr, properties, options);
}
inline void CheckSubscribe(
const net::MockClientMessageHandler::SubscribeCall& call,
std::initializer_list<std::string_view> prefixes,
const PubSubOptionsImpl& options = {}) {
REQUIRE(call.prefixes.size() == prefixes.size());
auto prefixIt = prefixes.begin();
for (const auto& prefix : call.prefixes) {
CHECK(prefix == *prefixIt);
++prefixIt;
}
CHECK(call.options == options);
}
inline void CheckSetValue(
const net::MockClientMessageHandler::SetValueCall& call, int pubuid,
const Value& value) {
CHECK(call.pubuid == pubuid);
CHECK(call.value == value);
}
struct ClientMessageCounts {
size_t publish = 0;
size_t unpublish = 0;
size_t setProperties = 0;
size_t subscribe = 0;
size_t unsubscribe = 0;
size_t setValue = 0;
};
inline void CheckClientMessageCounts(
const net::MockClientMessageHandler& handler,
const ClientMessageCounts& expected = {}) {
REQUIRE(handler.calls.size() ==
expected.publish + expected.unpublish + expected.setProperties +
expected.subscribe + expected.unsubscribe + expected.setValue);
REQUIRE(handler.publishCalls.size() == expected.publish);
REQUIRE(handler.unpublishCalls.size() == expected.unpublish);
REQUIRE(handler.setPropertiesCalls.size() == expected.setProperties);
REQUIRE(handler.subscribeCalls.size() == expected.subscribe);
REQUIRE(handler.unsubscribeCalls.size() == expected.unsubscribe);
REQUIRE(handler.setValueCalls.size() == expected.setValue);
}
inline void CheckNoClientCalls(const net::MockClientMessageHandler& handler) {
CheckClientMessageCounts(handler);
}
struct ServerMessageCounts {
size_t announce = 0;
size_t unannounce = 0;
size_t propertiesUpdate = 0;
size_t setValue = 0;
};
inline void CheckServerMessageCounts(
const net::MockServerMessageHandler& handler,
const ServerMessageCounts& expected = {}) {
REQUIRE(handler.calls.size() == expected.announce + expected.unannounce +
expected.propertiesUpdate +
expected.setValue);
REQUIRE(handler.announceCalls.size() == expected.announce);
REQUIRE(handler.unannounceCalls.size() == expected.unannounce);
REQUIRE(handler.propertiesUpdateCalls.size() == expected.propertiesUpdate);
REQUIRE(handler.setValueCalls.size() == expected.setValue);
}
inline void CheckNoServerCalls(const net::MockServerMessageHandler& handler) {
CheckServerMessageCounts(handler);
}
struct ListenerStorageCounts {
size_t activate = 0;
size_t connectionNotify = 0;
size_t topicNotify = 0;
size_t valueNotify = 0;
size_t logNotify = 0;
size_t timeSyncNotify = 0;
};
inline void CheckListenerStorageCounts(
const MockListenerStorage& listenerStorage,
const ListenerStorageCounts& expected = {}) {
REQUIRE(listenerStorage.activateCalls.size() == expected.activate);
REQUIRE(listenerStorage.connectionNotifyCalls.size() ==
expected.connectionNotify);
REQUIRE(listenerStorage.topicNotifyCalls.size() == expected.topicNotify);
REQUIRE(listenerStorage.valueNotifyCalls.size() == expected.valueNotify);
REQUIRE(listenerStorage.logNotifyCalls.size() == expected.logNotify);
REQUIRE(listenerStorage.timeSyncNotifyCalls.size() ==
expected.timeSyncNotify);
}
inline void CheckValueNotifyHandles(
const MockListenerStorage::ValueNotifyCall& call,
std::initializer_list<NT_Listener> expectedHandles) {
REQUIRE(call.handles.size() == expectedHandles.size());
CHECK(std::equal(call.handles.begin(), call.handles.end(),
expectedHandles.begin(), expectedHandles.end()));
}
inline void CheckNetworkCounts(const net::MockClientMessageHandler& network,
size_t publishCount, size_t subscribeCount,
size_t setValueCount, size_t unpublishCount = 0,
size_t setPropertiesCount = 0,
size_t unsubscribeCount = 0) {
CheckClientMessageCounts(network, {
.publish = publishCount,
.unpublish = unpublishCount,
.setProperties = setPropertiesCount,
.subscribe = subscribeCount,
.unsubscribe = unsubscribeCount,
.setValue = setValueCount,
});
std::vector<int> activePubs;
std::vector<int> activeSubs;
for (const auto& call : network.calls) {
if (auto publish =
std::get_if<net::MockClientMessageHandler::PublishCall>(&call)) {
activePubs.emplace_back(publish->pubuid);
} else if (auto unpublish =
std::get_if<net::MockClientMessageHandler::UnpublishCall>(
&call)) {
int pubuid = unpublish->pubuid;
CHECK(std::find(activePubs.begin(), activePubs.end(), pubuid) !=
activePubs.end());
std::erase(activePubs, pubuid);
} else if (auto subscribe =
std::get_if<net::MockClientMessageHandler::SubscribeCall>(
&call)) {
activeSubs.emplace_back(subscribe->subuid);
} else if (auto unsubscribe =
std::get_if<net::MockClientMessageHandler::UnsubscribeCall>(
&call)) {
int subuid = unsubscribe->subuid;
CHECK(std::find(activeSubs.begin(), activeSubs.end(), subuid) !=
activeSubs.end());
std::erase(activeSubs, subuid);
} else if (auto setValue =
std::get_if<net::MockClientMessageHandler::SetValueCall>(
&call)) {
int pubuid = setValue->pubuid;
CHECK(std::find(activePubs.begin(), activePubs.end(), pubuid) !=
activePubs.end());
}
}
}
} // namespace wpi::nt

View File

@@ -4,21 +4,43 @@
#pragma once
#include <deque>
#include <vector>
#include "IConnectionList.hpp"
#include "gmock/gmock.h"
namespace wpi::nt {
class MockConnectionList : public IConnectionList {
public:
MOCK_METHOD(int, AddConnection, (const ConnectionInfo& info), (override));
MOCK_METHOD(void, RemoveConnection, (int handle), (override));
MOCK_METHOD(void, ClearConnections, (), (override));
MOCK_METHOD(std::vector<ConnectionInfo>, GetConnections, (),
(const, override));
MOCK_METHOD(bool, IsConnected, (), (const, override));
int AddConnection(const ConnectionInfo& info) override {
addConnectionCalls.emplace_back(info);
if (!addConnectionReturns.empty()) {
int rv = addConnectionReturns.front();
addConnectionReturns.pop_front();
return rv;
}
return static_cast<int>(addConnectionCalls.size());
}
void RemoveConnection(int handle) override {
removeConnectionCalls.emplace_back(handle);
}
void ClearConnections() override { ++clearConnectionsCalls; }
std::vector<ConnectionInfo> GetConnections() const override {
return connections;
}
bool IsConnected() const override { return connected; }
std::deque<int> addConnectionReturns;
std::vector<ConnectionInfo> addConnectionCalls;
std::vector<int> removeConnectionCalls;
int clearConnectionsCalls = 0;
std::vector<ConnectionInfo> connections;
bool connected = false;
};
} // namespace wpi::nt

View File

@@ -6,40 +6,108 @@
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "IListenerStorage.hpp"
#include "gmock/gmock.h"
namespace wpi::nt {
class MockListenerStorage : public IListenerStorage {
public:
MOCK_METHOD(void, Activate,
(NT_Listener listenerHandle, unsigned int mask,
FinishEventFunc finishEvent),
(override));
MOCK_METHOD(void, Notify,
(std::span<const NT_Listener> handles, unsigned int flags,
std::span<ConnectionInfo const* const> infos),
(override));
MOCK_METHOD(void, Notify,
(std::span<const NT_Listener> handles, unsigned int flags,
std::span<const TopicInfo> infos),
(override));
MOCK_METHOD(void, Notify,
(std::span<const NT_Listener> handles, unsigned int flags,
NT_Topic topic, NT_Handle subentry, const Value& value),
(override));
MOCK_METHOD(void, Notify,
(unsigned int flags, unsigned int level,
std::string_view filename, unsigned int line,
std::string_view message),
(override));
MOCK_METHOD(void, NotifyTimeSync,
(std::span<const NT_Listener> handles, unsigned int flags,
int64_t serverTimeOffset, int64_t rtt2, bool valid),
(override));
struct ActivateCall {
NT_Listener listenerHandle;
unsigned int mask;
FinishEventFunc finishEvent;
};
struct ConnectionNotifyCall {
std::vector<NT_Listener> handles;
unsigned int flags;
std::vector<ConnectionInfo> infos;
};
struct TopicNotifyCall {
std::vector<NT_Listener> handles;
unsigned int flags;
std::vector<TopicInfo> infos;
};
struct ValueNotifyCall {
std::vector<NT_Listener> handles;
unsigned int flags;
NT_Topic topic;
NT_Handle subentry;
Value value;
};
struct LogNotifyCall {
unsigned int flags;
unsigned int level;
std::string filename;
unsigned int line;
std::string message;
};
struct TimeSyncNotifyCall {
std::vector<NT_Listener> handles;
unsigned int flags;
int64_t serverTimeOffset;
int64_t rtt2;
bool valid;
};
void Activate(NT_Listener listenerHandle, unsigned int mask,
FinishEventFunc finishEvent = {}) override {
activateCalls.emplace_back(listenerHandle, mask, std::move(finishEvent));
}
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
std::span<ConnectionInfo const* const> infos) override {
auto& call = connectionNotifyCalls.emplace_back();
call.handles.assign(handles.begin(), handles.end());
call.flags = flags;
for (auto info : infos) {
call.infos.emplace_back(*info);
}
}
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
std::span<const TopicInfo> infos) override {
topicNotifyCalls.emplace_back(
std::vector<NT_Listener>{handles.begin(), handles.end()}, flags,
std::vector<TopicInfo>{infos.begin(), infos.end()});
}
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
NT_Topic topic, NT_Handle subentry, const Value& value) override {
valueNotifyCalls.emplace_back(
std::vector<NT_Listener>{handles.begin(), handles.end()}, flags, topic,
subentry, value);
}
void Notify(unsigned int flags, unsigned int level, std::string_view filename,
unsigned int line, std::string_view message) override {
logNotifyCalls.emplace_back(flags, level, std::string{filename}, line,
std::string{message});
}
void NotifyTimeSync(std::span<const NT_Listener> handles, unsigned int flags,
int64_t serverTimeOffset, int64_t rtt2,
bool valid) override {
timeSyncNotifyCalls.emplace_back(
std::vector<NT_Listener>{handles.begin(), handles.end()}, flags,
serverTimeOffset, rtt2, valid);
}
std::vector<ActivateCall> activateCalls;
std::vector<ConnectionNotifyCall> connectionNotifyCalls;
std::vector<TopicNotifyCall> topicNotifyCalls;
std::vector<ValueNotifyCall> valueNotifyCalls;
std::vector<LogNotifyCall> logNotifyCalls;
std::vector<TimeSyncNotifyCall> timeSyncNotifyCalls;
};
} // namespace wpi::nt

View File

@@ -4,20 +4,54 @@
#pragma once
#include "gmock/gmock.h"
#include <initializer_list>
#include <string>
#include <string_view>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include "wpi/util/Logger.hpp"
namespace wpi {
class MockLogger : public wpi::util::Logger,
public ::testing::MockFunction<void(
unsigned int level, std::string_view file,
unsigned int line, std::string_view msg)> {
class MockLogger : public wpi::util::Logger {
public:
struct Message {
unsigned int level;
std::string file;
unsigned int line;
std::string msg;
};
MockLogger() {
SetLogger([this](unsigned int level, const char* file, unsigned int line,
const char* msg) { Call(level, file, line, msg); });
const char* msg) {
messages.emplace_back(level, file, line, msg);
});
}
struct ExpectedMessage {
unsigned int level;
std::string_view msg;
};
void CheckMessages(std::initializer_list<ExpectedMessage> expected) const {
REQUIRE(messages.size() == expected.size());
auto expectedIt = expected.begin();
for (const auto& message : messages) {
CHECK(message.level == expectedIt->level);
CHECK(std::string_view{message.msg} == expectedIt->msg);
++expectedIt;
}
}
void CheckMessage(unsigned int level, std::string_view expected) const {
CheckMessages({{level, expected}});
}
std::vector<Message> messages;
};
} // namespace wpi

View File

@@ -8,13 +8,14 @@
#include <string>
#include <thread>
#include <gtest/gtest.h>
#include <catch2/catch_message.hpp>
#include <catch2/catch_test_macros.hpp>
#include "wpi/nt/IntegerTopic.hpp"
#include "wpi/nt/NetworkTableInstance.hpp"
// Valid persistent JSON containing a single persistent integer topic.
static constexpr const char* kPersistentJson = R"([
static constexpr const char* PERSISTENT_JSON = R"([
{
"name": "/test/persistent_value",
"type": "int",
@@ -23,7 +24,12 @@ static constexpr const char* kPersistentJson = R"([
}
])";
class NetworkServerPersistentTest : public ::testing::Test {
static constexpr unsigned int RESTORE_BACKUP_PORT = 10040;
static constexpr unsigned int NORMAL_LOAD_PORT = 10041;
static constexpr unsigned int ORIGINAL_OVER_BACKUP_PORT = 10042;
static constexpr unsigned int NO_FILE_PORT = 10043;
class NetworkServerPersistentTest {
public:
NetworkServerPersistentTest() {
// Create a unique temp directory for each test
@@ -36,7 +42,7 @@ class NetworkServerPersistentTest : public ::testing::Test {
m_persistFile = (m_tempDir / "test_persistent.json").string();
}
~NetworkServerPersistentTest() override {
~NetworkServerPersistentTest() {
std::error_code ec;
std::filesystem::remove_all(m_tempDir, ec);
}
@@ -45,7 +51,8 @@ class NetworkServerPersistentTest : public ::testing::Test {
// Write content to a file.
static void WriteFile(const std::string& path, const std::string& content) {
std::ofstream os{path};
ASSERT_TRUE(os.is_open()) << "Failed to create file: " << path;
UNSCOPED_INFO("Failed to create file: " << path);
REQUIRE(os.is_open());
os << content;
}
@@ -73,13 +80,15 @@ class NetworkServerPersistentTest : public ::testing::Test {
// original persistent file is missing. This simulates SavePersistent being
// interrupted after renaming the original file to .bck but before the
// temporary file has been renamed to the original filename.
TEST_F(NetworkServerPersistentTest,
LoadPersistentRestoresFromBackupWhenOriginalMissing) {
TEST_CASE_METHOD(NetworkServerPersistentTest,
"NetworkServerPersistentTest "
"LoadPersistentRestoresFromBackupWhenOriginalMissing",
"[ntcore][network-server]") {
// Set up "interrupted" state: only .bck file exists, no original.
std::string backupFile = m_persistFile + ".bck";
WriteFile(backupFile, kPersistentJson);
ASSERT_TRUE(std::filesystem::exists(backupFile));
ASSERT_FALSE(std::filesystem::exists(m_persistFile));
WriteFile(backupFile, PERSISTENT_JSON);
REQUIRE(std::filesystem::exists(backupFile));
REQUIRE_FALSE(std::filesystem::exists(m_persistFile));
// Start a server that references the (missing) persistent file.
// Subscribe BEFORE starting the server so the server's local client has a
@@ -87,17 +96,17 @@ TEST_F(NetworkServerPersistentTest,
auto inst = wpi::nt::NetworkTableInstance::Create();
wpi::nt::IntegerSubscriber sub =
inst.GetIntegerTopic("/test/persistent_value").Subscribe(0);
inst.StartServer(m_persistFile, "127.0.0.1");
inst.StartServer(m_persistFile, "127.0.0.1", "", RESTORE_BACKUP_PORT);
// Wait for the persistent topic to appear.
EXPECT_TRUE(WaitForTopic(inst, "/test/persistent_value"))
<< "LoadPersistent did not restore from the .bck backup file";
UNSCOPED_INFO("LoadPersistent did not restore from the .bck backup file");
CHECK(WaitForTopic(inst, "/test/persistent_value"));
// Also verify the value is correct.
EXPECT_EQ(sub.Get(), 42);
CHECK(sub.Get() == 42);
// The .bck should have been renamed to the original filename.
EXPECT_TRUE(std::filesystem::exists(m_persistFile));
CHECK(std::filesystem::exists(m_persistFile));
inst.StopServer();
wpi::nt::NetworkTableInstance::Destroy(inst);
@@ -105,20 +114,22 @@ TEST_F(NetworkServerPersistentTest,
// Verify that LoadPersistent works normally when the original persistent file
// is present (no interruption scenario).
TEST_F(NetworkServerPersistentTest, LoadPersistentNormalLoad) {
TEST_CASE_METHOD(NetworkServerPersistentTest,
"NetworkServerPersistentTest LoadPersistentNormalLoad",
"[ntcore][network-server]") {
// Write the persistent file directly (no backup).
WriteFile(m_persistFile, kPersistentJson);
ASSERT_TRUE(std::filesystem::exists(m_persistFile));
WriteFile(m_persistFile, PERSISTENT_JSON);
REQUIRE(std::filesystem::exists(m_persistFile));
auto inst = wpi::nt::NetworkTableInstance::Create();
wpi::nt::IntegerSubscriber sub =
inst.GetIntegerTopic("/test/persistent_value").Subscribe(0);
inst.StartServer(m_persistFile, "127.0.0.1");
inst.StartServer(m_persistFile, "127.0.0.1", "", NORMAL_LOAD_PORT);
EXPECT_TRUE(WaitForTopic(inst, "/test/persistent_value"))
<< "LoadPersistent did not load the persistent file";
UNSCOPED_INFO("LoadPersistent did not load the persistent file");
CHECK(WaitForTopic(inst, "/test/persistent_value"));
EXPECT_EQ(sub.Get(), 42);
CHECK(sub.Get() == 42);
inst.StopServer();
wpi::nt::NetworkTableInstance::Destroy(inst);
@@ -126,9 +137,12 @@ TEST_F(NetworkServerPersistentTest, LoadPersistentNormalLoad) {
// Verify that when both the original file and .bck exist, the original file
// takes precedence (the backup is not used).
TEST_F(NetworkServerPersistentTest, LoadPersistentPrefersOriginalOverBackup) {
TEST_CASE_METHOD(
NetworkServerPersistentTest,
"NetworkServerPersistentTest LoadPersistentPrefersOriginalOverBackup",
"[ntcore][network-server]") {
// Original file with value 100.
static constexpr const char* kOriginalJson = R"([
static constexpr const char* ORIGINAL_JSON = R"([
{
"name": "/test/persistent_value",
"type": "int",
@@ -138,21 +152,21 @@ TEST_F(NetworkServerPersistentTest, LoadPersistentPrefersOriginalOverBackup) {
])";
// Backup file with a different value (42).
WriteFile(m_persistFile, kOriginalJson);
WriteFile(m_persistFile + ".bck", kPersistentJson);
ASSERT_TRUE(std::filesystem::exists(m_persistFile));
ASSERT_TRUE(std::filesystem::exists(m_persistFile + ".bck"));
WriteFile(m_persistFile, ORIGINAL_JSON);
WriteFile(m_persistFile + ".bck", PERSISTENT_JSON);
REQUIRE(std::filesystem::exists(m_persistFile));
REQUIRE(std::filesystem::exists(m_persistFile + ".bck"));
auto inst = wpi::nt::NetworkTableInstance::Create();
wpi::nt::IntegerSubscriber sub =
inst.GetIntegerTopic("/test/persistent_value").Subscribe(0);
inst.StartServer(m_persistFile, "127.0.0.1");
inst.StartServer(m_persistFile, "127.0.0.1", "", ORIGINAL_OVER_BACKUP_PORT);
EXPECT_TRUE(WaitForTopic(inst, "/test/persistent_value"))
<< "LoadPersistent did not load any persistent file";
UNSCOPED_INFO("LoadPersistent did not load any persistent file");
CHECK(WaitForTopic(inst, "/test/persistent_value"));
// The value should come from the original (100), not the backup (42).
EXPECT_EQ(sub.Get(), 100);
CHECK(sub.Get() == 100);
inst.StopServer();
wpi::nt::NetworkTableInstance::Destroy(inst);
@@ -160,19 +174,21 @@ TEST_F(NetworkServerPersistentTest, LoadPersistentPrefersOriginalOverBackup) {
// Verify that LoadPersistent handles a missing persistent file and no backup
// gracefully (no crash, no topics loaded).
TEST_F(NetworkServerPersistentTest, LoadPersistentNoFile) {
ASSERT_FALSE(std::filesystem::exists(m_persistFile));
ASSERT_FALSE(std::filesystem::exists(m_persistFile + ".bck"));
TEST_CASE_METHOD(NetworkServerPersistentTest,
"NetworkServerPersistentTest LoadPersistentNoFile",
"[ntcore][network-server]") {
REQUIRE_FALSE(std::filesystem::exists(m_persistFile));
REQUIRE_FALSE(std::filesystem::exists(m_persistFile + ".bck"));
auto inst = wpi::nt::NetworkTableInstance::Create();
inst.StartServer(m_persistFile, "127.0.0.1");
inst.StartServer(m_persistFile, "127.0.0.1", "", NO_FILE_PORT);
// Give the server time to initialize.
std::this_thread::sleep_for(std::chrono::milliseconds{500});
// No persistent topics should exist.
auto infos = inst.GetTopicInfo("/test/persistent_value");
EXPECT_TRUE(infos.empty());
CHECK(infos.empty());
inst.StopServer();
wpi::nt::NetworkTableInstance::Destroy(inst);

View File

@@ -7,104 +7,102 @@
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "TestPrinters.hpp"
#include "wpi/nt/NetworkTableInstance.hpp"
class NetworkTableTest : public ::testing::Test {};
TEST_F(NetworkTableTest, BasenameKey) {
EXPECT_EQ("simple", wpi::nt::NetworkTable::BasenameKey("simple"));
EXPECT_EQ("simple",
wpi::nt::NetworkTable::BasenameKey("one/two/many/simple"));
EXPECT_EQ("simple", wpi::nt::NetworkTable::BasenameKey(
"//////an/////awful/key////simple"));
TEST_CASE("NetworkTableTest BasenameKey", "[ntcore][network-table]") {
CHECK("simple" == wpi::nt::NetworkTable::BasenameKey("simple"));
CHECK("simple" == wpi::nt::NetworkTable::BasenameKey("one/two/many/simple"));
CHECK("simple" ==
wpi::nt::NetworkTable::BasenameKey("//////an/////awful/key////simple"));
}
TEST_F(NetworkTableTest, NormalizeKeySlash) {
EXPECT_EQ("/", wpi::nt::NetworkTable::NormalizeKey("///"));
EXPECT_EQ("/no/normal/req",
wpi::nt::NetworkTable::NormalizeKey("/no/normal/req"));
EXPECT_EQ("/no/leading/slash",
wpi::nt::NetworkTable::NormalizeKey("no/leading/slash"));
EXPECT_EQ("/what/an/awful/key/", wpi::nt::NetworkTable::NormalizeKey(
"//////what////an/awful/////key///"));
TEST_CASE("NetworkTableTest NormalizeKeySlash", "[ntcore][network-table]") {
CHECK("/" == wpi::nt::NetworkTable::NormalizeKey("///"));
CHECK("/no/normal/req" ==
wpi::nt::NetworkTable::NormalizeKey("/no/normal/req"));
CHECK("/no/leading/slash" ==
wpi::nt::NetworkTable::NormalizeKey("no/leading/slash"));
CHECK("/what/an/awful/key/" == wpi::nt::NetworkTable::NormalizeKey(
"//////what////an/awful/////key///"));
}
TEST_F(NetworkTableTest, NormalizeKeyNoSlash) {
EXPECT_EQ("a", wpi::nt::NetworkTable::NormalizeKey("a", false));
EXPECT_EQ("a", wpi::nt::NetworkTable::NormalizeKey("///a", false));
EXPECT_EQ("leading/slash",
wpi::nt::NetworkTable::NormalizeKey("/leading/slash", false));
EXPECT_EQ("no/leading/slash",
wpi::nt::NetworkTable::NormalizeKey("no/leading/slash", false));
EXPECT_EQ("what/an/awful/key/",
wpi::nt::NetworkTable::NormalizeKey(
"//////what////an/awful/////key///", false));
TEST_CASE("NetworkTableTest NormalizeKeyNoSlash", "[ntcore][network-table]") {
CHECK("a" == wpi::nt::NetworkTable::NormalizeKey("a", false));
CHECK("a" == wpi::nt::NetworkTable::NormalizeKey("///a", false));
CHECK("leading/slash" ==
wpi::nt::NetworkTable::NormalizeKey("/leading/slash", false));
CHECK("no/leading/slash" ==
wpi::nt::NetworkTable::NormalizeKey("no/leading/slash", false));
CHECK("what/an/awful/key/" ==
wpi::nt::NetworkTable::NormalizeKey("//////what////an/awful/////key///",
false));
}
TEST_F(NetworkTableTest, GetHierarchyEmpty) {
TEST_CASE("NetworkTableTest GetHierarchyEmpty", "[ntcore][network-table]") {
std::vector<std::string> expected{"/"};
ASSERT_EQ(expected, wpi::nt::NetworkTable::GetHierarchy(""));
REQUIRE(expected == wpi::nt::NetworkTable::GetHierarchy(""));
}
TEST_F(NetworkTableTest, GetHierarchyRoot) {
TEST_CASE("NetworkTableTest GetHierarchyRoot", "[ntcore][network-table]") {
std::vector<std::string> expected{"/"};
ASSERT_EQ(expected, wpi::nt::NetworkTable::GetHierarchy("/"));
REQUIRE(expected == wpi::nt::NetworkTable::GetHierarchy("/"));
}
TEST_F(NetworkTableTest, GetHierarchyNormal) {
TEST_CASE("NetworkTableTest GetHierarchyNormal", "[ntcore][network-table]") {
std::vector<std::string> expected{"/", "/foo", "/foo/bar", "/foo/bar/baz"};
ASSERT_EQ(expected, wpi::nt::NetworkTable::GetHierarchy("/foo/bar/baz"));
REQUIRE(expected == wpi::nt::NetworkTable::GetHierarchy("/foo/bar/baz"));
}
TEST_F(NetworkTableTest, GetHierarchyTrailingSlash) {
TEST_CASE("NetworkTableTest GetHierarchyTrailingSlash",
"[ntcore][network-table]") {
std::vector<std::string> expected{"/", "/foo", "/foo/bar", "/foo/bar/"};
ASSERT_EQ(expected, wpi::nt::NetworkTable::GetHierarchy("/foo/bar/"));
REQUIRE(expected == wpi::nt::NetworkTable::GetHierarchy("/foo/bar/"));
}
TEST_F(NetworkTableTest, ContainsKey) {
TEST_CASE("NetworkTableTest ContainsKey", "[ntcore][network-table]") {
auto inst = wpi::nt::NetworkTableInstance::Create();
auto nt = inst.GetTable("containskey");
ASSERT_FALSE(nt->ContainsKey("testkey"));
REQUIRE_FALSE(nt->ContainsKey("testkey"));
nt->PutNumber("testkey", 5);
ASSERT_TRUE(nt->ContainsKey("testkey"));
ASSERT_TRUE(inst.GetEntry("/containskey/testkey").Exists());
ASSERT_FALSE(inst.GetEntry("containskey/testkey").Exists());
REQUIRE(nt->ContainsKey("testkey"));
REQUIRE(inst.GetEntry("/containskey/testkey").Exists());
REQUIRE_FALSE(inst.GetEntry("containskey/testkey").Exists());
wpi::nt::NetworkTableInstance::Destroy(inst);
}
TEST_F(NetworkTableTest, LeadingSlash) {
TEST_CASE("NetworkTableTest LeadingSlash", "[ntcore][network-table]") {
auto inst = wpi::nt::NetworkTableInstance::Create();
auto nt = inst.GetTable("leadingslash");
auto nt2 = inst.GetTable("/leadingslash");
ASSERT_FALSE(nt->ContainsKey("testkey"));
REQUIRE_FALSE(nt->ContainsKey("testkey"));
nt2->PutNumber("testkey", 5);
ASSERT_TRUE(nt->ContainsKey("testkey"));
ASSERT_TRUE(inst.GetEntry("/leadingslash/testkey").Exists());
REQUIRE(nt->ContainsKey("testkey"));
REQUIRE(inst.GetEntry("/leadingslash/testkey").Exists());
wpi::nt::NetworkTableInstance::Destroy(inst);
}
TEST_F(NetworkTableTest, EmptyOrNoSlash) {
TEST_CASE("NetworkTableTest EmptyOrNoSlash", "[ntcore][network-table]") {
auto inst = wpi::nt::NetworkTableInstance::Create();
auto nt = inst.GetTable("/");
auto nt2 = inst.GetTable("");
ASSERT_FALSE(nt->ContainsKey("testkey"));
REQUIRE_FALSE(nt->ContainsKey("testkey"));
nt2->PutNumber("testkey", 5);
ASSERT_TRUE(nt->ContainsKey("testkey"));
ASSERT_TRUE(inst.GetEntry("/testkey").Exists());
REQUIRE(nt->ContainsKey("testkey"));
REQUIRE(inst.GetEntry("/testkey").Exists());
wpi::nt::NetworkTableInstance::Destroy(inst);
}
TEST_F(NetworkTableTest, ResetInstance) {
TEST_CASE("NetworkTableTest ResetInstance", "[ntcore][network-table]") {
auto inst = wpi::nt::NetworkTableInstance::Create();
auto nt = inst.GetTable("containskey");
ASSERT_FALSE(nt->ContainsKey("testkey"));
REQUIRE_FALSE(nt->ContainsKey("testkey"));
nt->PutNumber("testkey", 5);
ASSERT_TRUE(nt->ContainsKey("testkey"));
ASSERT_TRUE(inst.GetEntry("/containskey/testkey").Exists());
REQUIRE(nt->ContainsKey("testkey"));
REQUIRE(inst.GetEntry("/containskey/testkey").Exists());
wpi::nt::ResetInstance(inst.GetHandle());
ASSERT_FALSE(nt->ContainsKey("testkey"));
REQUIRE_FALSE(nt->ContainsKey("testkey"));
wpi::nt::NetworkTableInstance::Destroy(inst);
}

View File

@@ -1,47 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "PubSubOptionsMatcher.hpp"
#include "TestPrinters.hpp"
namespace wpi::nt {
bool PubSubOptionsMatcher::MatchAndExplain(
const PubSubOptionsImpl& val,
::testing::MatchResultListener* listener) const {
bool match = true;
if (val.periodicMs != good.periodicMs) {
*listener << "periodic mismatch ";
match = false;
}
if (val.pollStorage != good.pollStorage) {
*listener << "pollStorage mismatch ";
match = false;
}
if (val.sendAll != good.sendAll) {
*listener << "sendAll mismatch ";
match = false;
}
if (val.keepDuplicates != good.keepDuplicates) {
*listener << "keepDuplicates mismatch ";
match = false;
}
if (val.disableSignal != good.disableSignal) {
*listener << "disableSignal mismatch ";
match = false;
}
return match;
}
void PubSubOptionsMatcher::DescribeTo(::std::ostream* os) const {
PrintTo(good, os);
}
void PubSubOptionsMatcher::DescribeNegationTo(::std::ostream* os) const {
*os << "is not equal to ";
PrintTo(good, os);
}
} // namespace wpi::nt

View File

@@ -4,32 +4,19 @@
#pragma once
#include <ostream>
#include <utility>
#include "PubSubOptions.hpp"
#include "gmock/gmock.h"
#include "TestPrinters.hpp"
namespace wpi::nt {
class PubSubOptionsMatcher
: public ::testing::MatcherInterface<const PubSubOptionsImpl&> {
public:
explicit PubSubOptionsMatcher(PubSubOptionsImpl good)
: good{std::move(good)} {}
bool MatchAndExplain(const PubSubOptionsImpl& val,
::testing::MatchResultListener* listener) const override;
void DescribeTo(::std::ostream* os) const override;
void DescribeNegationTo(::std::ostream* os) const override;
private:
PubSubOptionsImpl good;
};
inline ::testing::Matcher<const PubSubOptionsImpl&> PubSubOptionsEq(
PubSubOptionsImpl good) {
return ::testing::MakeMatcher(new PubSubOptionsMatcher(std::move(good)));
inline bool operator==(const PubSubOptionsImpl& lhs,
const PubSubOptionsImpl& rhs) {
return lhs.periodicMs == rhs.periodicMs &&
lhs.pollStorage == rhs.pollStorage && lhs.sendAll == rhs.sendAll &&
lhs.keepDuplicates == rhs.keepDuplicates &&
lhs.topicsOnly == rhs.topicsOnly &&
lhs.prefixMatch == rhs.prefixMatch &&
lhs.disableSignal == rhs.disableSignal;
}
} // namespace wpi::nt

View File

@@ -29,7 +29,7 @@ class StorageTest {
void HookOutgoing(bool server) { storage.SetDispatcher(&dispatcher, server); }
wpi::util::Logger logger;
::testing::StrictMock<MockDispatcher> dispatcher;
MockDispatcher dispatcher;
Storage storage;
Storage::Entry tmp_entry;
};

View File

@@ -4,12 +4,12 @@
#include "wpi/util/struct/Struct.hpp"
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "wpi/nt/NetworkTableInstance.hpp"
#include "wpi/nt/StructArrayTopic.hpp"
#include "wpi/nt/StructTopic.hpp"
#include "wpi/util/SpanMatcher.hpp"
#include "wpi/util/json.hpp"
namespace {
struct Inner {
@@ -157,189 +157,193 @@ struct wpi::util::Struct<ThingB, Info1> {
namespace wpi::nt {
class StructTest : public ::testing::Test {
class StructTest {
public:
StructTest() { inst = wpi::nt::NetworkTableInstance::Create(); }
~StructTest() override { wpi::nt::NetworkTableInstance::Destroy(inst); }
~StructTest() { wpi::nt::NetworkTableInstance::Destroy(inst); }
wpi::nt::NetworkTableInstance inst;
};
TEST_F(StructTest, InnerConstexpr) {
TEST_CASE_METHOD(StructTest, "StructTest InnerConstexpr", "[ntcore][struct]") {
wpi::nt::StructTopic<Inner> topic = inst.GetStructTopic<Inner>("inner");
wpi::nt::StructPublisher<Inner> pub = topic.Publish();
wpi::nt::StructSubscriber<Inner> sub = topic.Subscribe({});
ASSERT_EQ(topic.GetTypeString(), "struct:Inner");
REQUIRE(topic.GetTypeString() == "struct:Inner");
pub.SetDefault({0, 1});
Inner val = sub.Get();
ASSERT_EQ(val.a, 0);
ASSERT_EQ(val.b, 1);
REQUIRE(val.a == 0);
REQUIRE(val.b == 1);
pub.Set({1, 2});
auto atomicVal = sub.GetAtomic();
ASSERT_EQ(atomicVal.value.a, 1);
ASSERT_EQ(atomicVal.value.b, 2);
REQUIRE(atomicVal.value.a == 1);
REQUIRE(atomicVal.value.b == 2);
Inner val2;
sub.GetInto(&val2);
ASSERT_EQ(val2.a, 1);
ASSERT_EQ(val2.b, 2);
REQUIRE(val2.a == 1);
REQUIRE(val2.b == 2);
auto vals = sub.ReadQueue();
ASSERT_EQ(vals.size(), 1u);
ASSERT_EQ(vals[0].value.a, 1);
ASSERT_EQ(vals[0].value.b, 2);
REQUIRE(vals.size() == 1u);
REQUIRE(vals[0].value.a == 1);
REQUIRE(vals[0].value.b == 2);
}
TEST_F(StructTest, InnerNonconstexpr) {
TEST_CASE_METHOD(StructTest, "StructTest InnerNonconstexpr",
"[ntcore][struct]") {
wpi::nt::StructTopic<Inner2> topic = inst.GetStructTopic<Inner2>("inner2");
wpi::nt::StructPublisher<Inner2> pub = topic.Publish();
wpi::nt::StructSubscriber<Inner2> sub = topic.Subscribe({});
ASSERT_EQ(topic.GetTypeString(), "struct:Inner2");
REQUIRE(topic.GetTypeString() == "struct:Inner2");
pub.SetDefault({0, 1});
Inner2 val = sub.Get();
ASSERT_EQ(val.a, 0);
ASSERT_EQ(val.b, 1);
REQUIRE(val.a == 0);
REQUIRE(val.b == 1);
pub.Set({1, 2});
auto atomicVal = sub.GetAtomic();
ASSERT_EQ(atomicVal.value.a, 1);
ASSERT_EQ(atomicVal.value.b, 2);
REQUIRE(atomicVal.value.a == 1);
REQUIRE(atomicVal.value.b == 2);
Inner2 val2;
sub.GetInto(&val2);
ASSERT_EQ(val2.a, 1);
ASSERT_EQ(val2.b, 2);
REQUIRE(val2.a == 1);
REQUIRE(val2.b == 2);
auto vals = sub.ReadQueue();
ASSERT_EQ(vals.size(), 1u);
ASSERT_EQ(vals[0].value.a, 1);
ASSERT_EQ(vals[0].value.b, 2);
REQUIRE(vals.size() == 1u);
REQUIRE(vals[0].value.a == 1);
REQUIRE(vals[0].value.b == 2);
}
TEST_F(StructTest, OuterConstexpr) {
TEST_CASE_METHOD(StructTest, "StructTest OuterConstexpr", "[ntcore][struct]") {
wpi::nt::StructTopic<Outer> topic = inst.GetStructTopic<Outer>("outer");
wpi::nt::StructPublisher<Outer> pub = topic.Publish();
wpi::nt::StructSubscriber<Outer> sub = topic.Subscribe({});
ASSERT_EQ(topic.GetTypeString(), "struct:Outer");
REQUIRE(topic.GetTypeString() == "struct:Outer");
pub.SetDefault({{0, 1}, 2});
Outer val = sub.Get();
ASSERT_EQ(val.inner.a, 0);
ASSERT_EQ(val.inner.b, 1);
ASSERT_EQ(val.c, 2);
REQUIRE(val.inner.a == 0);
REQUIRE(val.inner.b == 1);
REQUIRE(val.c == 2);
pub.Set({{1, 2}, 3});
auto atomicVal = sub.GetAtomic();
ASSERT_EQ(atomicVal.value.inner.a, 1);
ASSERT_EQ(atomicVal.value.inner.b, 2);
ASSERT_EQ(atomicVal.value.c, 3);
REQUIRE(atomicVal.value.inner.a == 1);
REQUIRE(atomicVal.value.inner.b == 2);
REQUIRE(atomicVal.value.c == 3);
Outer val2;
sub.GetInto(&val2);
ASSERT_EQ(val2.inner.a, 1);
ASSERT_EQ(val2.inner.b, 2);
ASSERT_EQ(val2.c, 3);
REQUIRE(val2.inner.a == 1);
REQUIRE(val2.inner.b == 2);
REQUIRE(val2.c == 3);
auto vals = sub.ReadQueue();
ASSERT_EQ(vals.size(), 1u);
ASSERT_EQ(vals[0].value.inner.a, 1);
ASSERT_EQ(vals[0].value.inner.b, 2);
ASSERT_EQ(vals[0].value.c, 3);
REQUIRE(vals.size() == 1u);
REQUIRE(vals[0].value.inner.a == 1);
REQUIRE(vals[0].value.inner.b == 2);
REQUIRE(vals[0].value.c == 3);
}
TEST_F(StructTest, OuterNonconstexpr) {
TEST_CASE_METHOD(StructTest, "StructTest OuterNonconstexpr",
"[ntcore][struct]") {
wpi::nt::StructTopic<Outer2> topic = inst.GetStructTopic<Outer2>("outer2");
wpi::nt::StructPublisher<Outer2> pub = topic.Publish();
wpi::nt::StructSubscriber<Outer2> sub = topic.Subscribe({});
ASSERT_EQ(topic.GetTypeString(), "struct:Outer2");
REQUIRE(topic.GetTypeString() == "struct:Outer2");
pub.SetDefault({{0, 1}, 2});
Outer2 val = sub.Get();
ASSERT_EQ(val.inner.a, 0);
ASSERT_EQ(val.inner.b, 1);
ASSERT_EQ(val.c, 2);
REQUIRE(val.inner.a == 0);
REQUIRE(val.inner.b == 1);
REQUIRE(val.c == 2);
pub.Set({{1, 2}, 3});
auto atomicVal = sub.GetAtomic();
ASSERT_EQ(atomicVal.value.inner.a, 1);
ASSERT_EQ(atomicVal.value.inner.b, 2);
ASSERT_EQ(atomicVal.value.c, 3);
REQUIRE(atomicVal.value.inner.a == 1);
REQUIRE(atomicVal.value.inner.b == 2);
REQUIRE(atomicVal.value.c == 3);
Outer2 val2;
sub.GetInto(&val2);
ASSERT_EQ(val2.inner.a, 1);
ASSERT_EQ(val2.inner.b, 2);
ASSERT_EQ(val2.c, 3);
REQUIRE(val2.inner.a == 1);
REQUIRE(val2.inner.b == 2);
REQUIRE(val2.c == 3);
auto vals = sub.ReadQueue();
ASSERT_EQ(vals.size(), 1u);
ASSERT_EQ(vals[0].value.inner.a, 1);
ASSERT_EQ(vals[0].value.inner.b, 2);
ASSERT_EQ(vals[0].value.c, 3);
REQUIRE(vals.size() == 1u);
REQUIRE(vals[0].value.inner.a == 1);
REQUIRE(vals[0].value.inner.b == 2);
REQUIRE(vals[0].value.c == 3);
}
TEST_F(StructTest, InnerArrayConstexpr) {
TEST_CASE_METHOD(StructTest, "StructTest InnerArrayConstexpr",
"[ntcore][struct]") {
wpi::nt::StructArrayTopic<Inner> topic =
inst.GetStructArrayTopic<Inner>("innerA");
wpi::nt::StructArrayPublisher<Inner> pub = topic.Publish();
wpi::nt::StructArraySubscriber<Inner> sub = topic.Subscribe({});
ASSERT_EQ(topic.GetTypeString(), "struct:Inner[]");
REQUIRE(topic.GetTypeString() == "struct:Inner[]");
pub.SetDefault({{{0, 1}}});
auto val = sub.Get();
ASSERT_EQ(val.size(), 1u);
ASSERT_EQ(val[0].a, 0);
ASSERT_EQ(val[0].b, 1);
REQUIRE(val.size() == 1u);
REQUIRE(val[0].a == 0);
REQUIRE(val[0].b == 1);
pub.Set({{{1, 2}}});
auto atomicVal = sub.GetAtomic();
ASSERT_EQ(atomicVal.value.size(), 1u);
ASSERT_EQ(atomicVal.value[0].a, 1);
ASSERT_EQ(atomicVal.value[0].b, 2);
REQUIRE(atomicVal.value.size() == 1u);
REQUIRE(atomicVal.value[0].a == 1);
REQUIRE(atomicVal.value[0].b == 2);
auto vals = sub.ReadQueue();
ASSERT_EQ(vals.size(), 1u);
ASSERT_EQ(vals[0].value.size(), 1u);
ASSERT_EQ(vals[0].value[0].a, 1);
ASSERT_EQ(vals[0].value[0].b, 2);
REQUIRE(vals.size() == 1u);
REQUIRE(vals[0].value.size() == 1u);
REQUIRE(vals[0].value[0].a == 1);
REQUIRE(vals[0].value[0].b == 2);
}
TEST_F(StructTest, InnerArrayNonconstexpr) {
TEST_CASE_METHOD(StructTest, "StructTest InnerArrayNonconstexpr",
"[ntcore][struct]") {
wpi::nt::StructArrayTopic<Inner2> topic =
inst.GetStructArrayTopic<Inner2>("innerA2");
wpi::nt::StructArrayPublisher<Inner2> pub = topic.Publish();
wpi::nt::StructArraySubscriber<Inner2> sub = topic.Subscribe({});
ASSERT_EQ(topic.GetTypeString(), "struct:Inner2[]");
REQUIRE(topic.GetTypeString() == "struct:Inner2[]");
pub.SetDefault({{{0, 1}}});
auto val = sub.Get();
ASSERT_EQ(val.size(), 1u);
ASSERT_EQ(val[0].a, 0);
ASSERT_EQ(val[0].b, 1);
REQUIRE(val.size() == 1u);
REQUIRE(val[0].a == 0);
REQUIRE(val[0].b == 1);
pub.Set({{{1, 2}}});
auto atomicVal = sub.GetAtomic();
ASSERT_EQ(atomicVal.value.size(), 1u);
ASSERT_EQ(atomicVal.value[0].a, 1);
ASSERT_EQ(atomicVal.value[0].b, 2);
REQUIRE(atomicVal.value.size() == 1u);
REQUIRE(atomicVal.value[0].a == 1);
REQUIRE(atomicVal.value[0].b == 2);
auto vals = sub.ReadQueue();
ASSERT_EQ(vals.size(), 1u);
ASSERT_EQ(vals[0].value.size(), 1u);
ASSERT_EQ(vals[0].value[0].a, 1);
ASSERT_EQ(vals[0].value[0].b, 2);
REQUIRE(vals.size() == 1u);
REQUIRE(vals[0].value.size() == 1u);
REQUIRE(vals[0].value[0].a == 1);
REQUIRE(vals[0].value[0].b == 2);
}
TEST_F(StructTest, StructA) {
TEST_CASE_METHOD(StructTest, "StructTest StructA", "[ntcore][struct]") {
wpi::nt::StructTopic<ThingA> topic = inst.GetStructTopic<ThingA>("a");
wpi::nt::StructPublisher<ThingA> pub = topic.Publish();
wpi::nt::StructPublisher<ThingA> pub2 =
@@ -357,7 +361,7 @@ TEST_F(StructTest, StructA) {
entry.Get({});
}
TEST_F(StructTest, StructArrayA) {
TEST_CASE_METHOD(StructTest, "StructTest StructArrayA", "[ntcore][struct]") {
wpi::nt::StructArrayTopic<ThingA> topic =
inst.GetStructArrayTopic<ThingA>("a");
wpi::nt::StructArrayPublisher<ThingA> pub = topic.Publish();
@@ -376,7 +380,8 @@ TEST_F(StructTest, StructArrayA) {
entry.Get({});
}
TEST_F(StructTest, StructFixedArrayA) {
TEST_CASE_METHOD(StructTest, "StructTest StructFixedArrayA",
"[ntcore][struct]") {
wpi::nt::StructTopic<std::array<ThingA, 2>> topic =
inst.GetStructTopic<std::array<ThingA, 2>>("a");
wpi::nt::StructPublisher<std::array<ThingA, 2>> pub = topic.Publish();
@@ -396,7 +401,7 @@ TEST_F(StructTest, StructFixedArrayA) {
entry.Get(arr);
}
TEST_F(StructTest, StructB) {
TEST_CASE_METHOD(StructTest, "StructTest StructB", "[ntcore][struct]") {
Info1 info;
wpi::nt::StructTopic<ThingB, Info1> topic =
inst.GetStructTopic<ThingB, Info1>("b", info);
@@ -416,7 +421,7 @@ TEST_F(StructTest, StructB) {
entry.Get({});
}
TEST_F(StructTest, StructArrayB) {
TEST_CASE_METHOD(StructTest, "StructTest StructArrayB", "[ntcore][struct]") {
Info1 info;
wpi::nt::StructArrayTopic<ThingB, Info1> topic =
inst.GetStructArrayTopic<ThingB, Info1>("b", info);
@@ -436,7 +441,8 @@ TEST_F(StructTest, StructArrayB) {
entry.Get({});
}
TEST_F(StructTest, StructFixedArrayB) {
TEST_CASE_METHOD(StructTest, "StructTest StructFixedArrayB",
"[ntcore][struct]") {
Info1 info;
wpi::nt::StructTopic<std::array<ThingB, 2>, Info1> topic =
inst.GetStructTopic<std::array<ThingB, 2>, Info1>("b", info);

View File

@@ -3,31 +3,21 @@
// the WPILib BSD license file in the root directory of this project.
#include <memory>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "TestPrinters.hpp"
#include "gmock/gmock.h"
#include "wpi/nt/DoubleTopic.hpp"
#include "wpi/nt/NetworkTableInstance.hpp"
#include "wpi/nt/ntcore_cpp.hpp"
using ::testing::_;
using MockTableEventListener = testing::MockFunction<void(
wpi::nt::NetworkTable* table, std::string_view key,
const wpi::nt::Event& event)>;
using MockSubTableListener = testing::MockFunction<void(
wpi::nt::NetworkTable* parent, std::string_view name,
std::shared_ptr<wpi::nt::NetworkTable> table)>;
class TableListenerTest : public ::testing::Test {
class TableListenerTest {
public:
TableListenerTest() : m_inst(wpi::nt::NetworkTableInstance::Create()) {}
~TableListenerTest() override {
wpi::nt::NetworkTableInstance::Destroy(m_inst);
}
~TableListenerTest() { wpi::nt::NetworkTableInstance::Destroy(m_inst); }
void PublishTopics();
@@ -44,21 +34,44 @@ void TableListenerTest::PublishTopics() {
m_bazvalue = m_inst.GetDoubleTopic("/baz/bazvalue").Publish();
}
TEST_F(TableListenerTest, AddListener) {
TEST_CASE_METHOD(TableListenerTest, "TableListenerTest AddListener",
"[ntcore][table-listener]") {
auto table = m_inst.GetTable("/foo");
MockTableEventListener listener;
struct ListenerCall {
wpi::nt::NetworkTable* table;
std::string key;
};
std::vector<ListenerCall> listenerCalls;
table->AddListener(NT_EVENT_TOPIC | NT_EVENT_IMMEDIATE,
listener.AsStdFunction());
EXPECT_CALL(listener, Call(table.get(), std::string_view{"foovalue"}, _));
[&](wpi::nt::NetworkTable* callbackTable,
std::string_view key, const wpi::nt::Event&) {
listenerCalls.emplace_back(callbackTable,
std::string{key});
});
PublishTopics();
EXPECT_TRUE(m_inst.WaitForListenerQueue(1.0));
CHECK(m_inst.WaitForListenerQueue(1.0));
REQUIRE(listenerCalls.size() == 1u);
CHECK(listenerCalls[0].table == table.get());
CHECK(listenerCalls[0].key == "foovalue");
}
TEST_F(TableListenerTest, AddSubTableListener) {
TEST_CASE_METHOD(TableListenerTest, "TableListenerTest AddSubTableListener",
"[ntcore][table-listener]") {
auto table = m_inst.GetTable("/foo");
MockSubTableListener listener;
table->AddSubTableListener(listener.AsStdFunction());
EXPECT_CALL(listener, Call(table.get(), std::string_view{"bar"}, _));
struct ListenerCall {
wpi::nt::NetworkTable* parent;
std::string name;
std::shared_ptr<wpi::nt::NetworkTable> table;
};
std::vector<ListenerCall> listenerCalls;
table->AddSubTableListener(
[&](wpi::nt::NetworkTable* parent, std::string_view name,
std::shared_ptr<wpi::nt::NetworkTable> callbackTable) {
listenerCalls.emplace_back(parent, std::string{name}, callbackTable);
});
PublishTopics();
EXPECT_TRUE(m_inst.WaitForListenerQueue(1.0));
CHECK(m_inst.WaitForListenerQueue(1.0));
REQUIRE(listenerCalls.size() == 1u);
CHECK(listenerCalls[0].parent == table.get());
CHECK(listenerCalls[0].name == "bar");
}

View File

@@ -4,106 +4,295 @@
#include "TestPrinters.hpp"
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include "Handle.hpp"
#include "PubSubOptions.hpp"
#include "net/Message.hpp"
#include "wpi/nt/NetworkTableValue.hpp"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/fmt/raw_ostream.hpp"
#include "wpi/util/json.hpp"
#include "wpi/util/raw_ostream.hpp"
namespace wpi::nt {
namespace {
void PrintTo(const Event& event, std::ostream* os) {
*os << "Event{listener=";
PrintTo(Handle{event.listener}, os);
*os << ", flags=" << event.flags;
// *os << ", name=\"" << event.name << "\", flags=" << event.flags
// << "value=";
// PrintTo(event.value, os);
*os << '}';
template <typename T>
void WriteRangeElement(const T& elem, wpi::util::raw_ostream& os) {
using Elem = std::decay_t<T>;
if constexpr (std::is_convertible_v<Elem, std::string_view>) {
os << elem;
} else {
wpi::util::print(os, "{}", elem);
}
}
void PrintTo(const Handle& handle, std::ostream* os) {
*os << "Handle{";
template <typename Range>
void WriteRange(const Range& range, wpi::util::raw_ostream& os) {
os << '{';
bool first = true;
for (const auto& elem : range) {
if (first) {
first = false;
} else {
os << ", ";
}
WriteRangeElement(elem, os);
}
os << '}';
}
template <typename Range>
void WriteStringRange(const Range& range, wpi::util::raw_ostream& os) {
os << '{';
bool first = true;
for (const auto& elem : range) {
if (first) {
first = false;
} else {
os << ", ";
}
os << '"' << elem << '"';
}
os << '}';
}
std::string JsonToString(const wpi::util::json& value) {
std::string out;
wpi::util::raw_string_ostream os{out};
value.marshal(os);
return os.str();
}
void WriteOptionalInt(std::optional<int> value, wpi::util::raw_ostream& os) {
if (value) {
wpi::util::print(os, "{}", *value);
} else {
os << "nullopt";
}
}
void WritePubSubOptions(const wpi::nt::PubSubOptionsImpl& options,
wpi::util::raw_ostream& os) {
wpi::util::print(os,
"PubSubOptions{{periodicMs={}, pollStorage={}, sendAll={}, "
"keepDuplicates={}, topicsOnly={}, prefixMatch={}, "
"disableSignal={}}}",
options.periodicMs, options.pollStorage, options.sendAll,
options.keepDuplicates, options.topicsOnly,
options.prefixMatch, options.disableSignal);
}
void WriteHandle(const wpi::nt::Handle& handle, wpi::util::raw_ostream& os) {
os << "Handle{";
switch (handle.GetType()) {
case Handle::LISTENER:
*os << "LISTENER";
case wpi::nt::Handle::LISTENER:
os << "LISTENER";
break;
case Handle::LISTENER_POLLER:
*os << "LISTENER_POLLER";
case wpi::nt::Handle::LISTENER_POLLER:
os << "LISTENER_POLLER";
break;
case Handle::ENTRY:
*os << "ENTRY";
case wpi::nt::Handle::ENTRY:
os << "ENTRY";
break;
case Handle::INSTANCE:
*os << "INSTANCE";
case wpi::nt::Handle::INSTANCE:
os << "INSTANCE";
break;
case Handle::TOPIC:
*os << "kTopic";
case wpi::nt::Handle::TOPIC:
os << "kTopic";
break;
case Handle::SUBSCRIBER:
*os << "SUBSCRIBER";
case wpi::nt::Handle::SUBSCRIBER:
os << "SUBSCRIBER";
break;
case Handle::PUBLISHER:
*os << "PUBLISHER";
case wpi::nt::Handle::PUBLISHER:
os << "PUBLISHER";
break;
default:
*os << "UNKNOWN";
os << "UNKNOWN";
break;
}
*os << ", " << handle.GetInst() << ", " << handle.GetIndex() << '}';
wpi::util::print(os, ", {}, {}}}", handle.GetInst(), handle.GetIndex());
}
void PrintTo(const Value& value, std::ostream* os) {
*os << "Value{";
void WriteValue(const wpi::nt::Value& value, wpi::util::raw_ostream& os) {
os << "Value{";
switch (value.type()) {
case NT_UNASSIGNED:
break;
case NT_BOOLEAN:
*os << "boolean, " << (value.GetBoolean() ? "true" : "false");
os << "boolean, " << (value.GetBoolean() ? "true" : "false");
break;
case NT_DOUBLE:
*os << "double, " << value.GetDouble();
wpi::util::print(os, "double, {}", value.GetDouble());
break;
case NT_FLOAT:
*os << "float, " << value.GetFloat();
wpi::util::print(os, "float, {}", value.GetFloat());
break;
case NT_INTEGER:
*os << "int, " << value.GetInteger();
wpi::util::print(os, "int, {}", value.GetInteger());
break;
case NT_STRING:
*os << "string, \"" << value.GetString() << '"';
os << "string, \"" << value.GetString() << '"';
break;
case NT_RAW:
*os << "raw, " << ::testing::PrintToString(value.GetRaw());
os << "raw, ";
WriteRange(value.GetRaw(), os);
break;
case NT_BOOLEAN_ARRAY:
*os << "boolean[], " << ::testing::PrintToString(value.GetBooleanArray());
os << "boolean[], ";
WriteRange(value.GetBooleanArray(), os);
break;
case NT_DOUBLE_ARRAY:
*os << "double[], " << ::testing::PrintToString(value.GetDoubleArray());
os << "double[], ";
WriteRange(value.GetDoubleArray(), os);
break;
case NT_FLOAT_ARRAY:
*os << "float[], " << ::testing::PrintToString(value.GetFloatArray());
os << "float[], ";
WriteRange(value.GetFloatArray(), os);
break;
case NT_INTEGER_ARRAY:
*os << "int[], " << ::testing::PrintToString(value.GetIntegerArray());
os << "int[], ";
WriteRange(value.GetIntegerArray(), os);
break;
case NT_STRING_ARRAY:
*os << "string[], " << ::testing::PrintToString(value.GetStringArray());
os << "string[], ";
WriteRange(value.GetStringArray(), os);
break;
default:
*os << "UNKNOWN TYPE " << value.type();
wpi::util::print(os, "UNKNOWN TYPE {}", static_cast<int>(value.type()));
break;
}
*os << '}';
os << '}';
}
void PrintTo(const PubSubOptionsImpl& options, std::ostream* os) {
*os << "PubSubOptions{periodicMs=" << options.periodicMs
<< ", pollStorage=" << options.pollStorage
<< ", sendAll=" << options.sendAll
<< ", keepDuplicates=" << options.keepDuplicates
<< ", disableSignal=" << options.disableSignal << '}';
void WriteClientMessage(const wpi::nt::net::ClientMessage& msg,
wpi::util::raw_ostream& os) {
std::visit(
[&](const auto& contents) {
using T = std::decay_t<decltype(contents)>;
if constexpr (std::is_same_v<T, std::monostate>) {
os << "ClientMessage{}";
} else if constexpr (std::is_same_v<T, wpi::nt::net::PublishMsg>) {
wpi::util::print(os,
"ClientMessage{{PublishMsg{{pubuid={}, name=\"{}\", "
"typeStr=\"{}\", properties={}, options=",
contents.pubuid, contents.name, contents.typeStr,
JsonToString(contents.properties));
WritePubSubOptions(contents.options, os);
os << "}}";
} else if constexpr (std::is_same_v<T, wpi::nt::net::UnpublishMsg>) {
wpi::util::print(os, "ClientMessage{{UnpublishMsg{{pubuid={}}}}}",
contents.pubuid);
} else if constexpr (std::is_same_v<T,
wpi::nt::net::SetPropertiesMsg>) {
wpi::util::print(os,
"ClientMessage{{SetPropertiesMsg{{name=\"{}\", "
"update={}}}}}",
contents.name, JsonToString(contents.update));
} else if constexpr (std::is_same_v<T, wpi::nt::net::SubscribeMsg>) {
wpi::util::print(os,
"ClientMessage{{SubscribeMsg{{subuid={}, "
"topicNames=",
contents.subuid);
WriteStringRange(contents.topicNames, os);
os << ", options=";
WritePubSubOptions(contents.options, os);
os << "}}";
} else if constexpr (std::is_same_v<T, wpi::nt::net::UnsubscribeMsg>) {
wpi::util::print(os, "ClientMessage{{UnsubscribeMsg{{subuid={}}}}}",
contents.subuid);
} else if constexpr (std::is_same_v<T, wpi::nt::net::ClientValueMsg>) {
wpi::util::print(os,
"ClientMessage{{ClientValueMsg{{pubuid={}, value=",
contents.pubuid);
WriteValue(contents.value, os);
os << "}}";
}
},
msg.contents);
}
} // namespace wpi::nt
void WriteServerMessage(const wpi::nt::net::ServerMessage& msg,
wpi::util::raw_ostream& os) {
std::visit(
[&](const auto& contents) {
using T = std::decay_t<decltype(contents)>;
if constexpr (std::is_same_v<T, std::monostate>) {
os << "ServerMessage{}";
} else if constexpr (std::is_same_v<T, wpi::nt::net::AnnounceMsg>) {
wpi::util::print(os,
"ServerMessage{{AnnounceMsg{{name=\"{}\", id={}, "
"typeStr=\"{}\", pubuid=",
contents.name, contents.id, contents.typeStr);
WriteOptionalInt(contents.pubuid, os);
wpi::util::print(os, ", properties={}}}}}",
JsonToString(contents.properties));
} else if constexpr (std::is_same_v<T, wpi::nt::net::UnannounceMsg>) {
wpi::util::print(
os, "ServerMessage{{UnannounceMsg{{name=\"{}\", id={}}}}}",
contents.name, contents.id);
} else if constexpr (std::is_same_v<
T, wpi::nt::net::PropertiesUpdateMsg>) {
wpi::util::print(os,
"ServerMessage{{PropertiesUpdateMsg{{name=\"{}\", "
"update={}, ack={}}}}}",
contents.name, JsonToString(contents.update),
contents.ack);
} else if constexpr (std::is_same_v<T, wpi::nt::net::ServerValueMsg>) {
wpi::util::print(os,
"ServerMessage{{ServerValueMsg{{topic={}, value=",
contents.topic);
WriteValue(contents.value, os);
os << "}}";
}
},
msg.contents);
}
template <typename T, typename Writer>
std::string ToString(const T& value, Writer writer) {
std::string out;
wpi::util::raw_string_ostream os{out};
writer(value, os);
return os.str();
}
} // namespace
std::string Catch::StringMaker<wpi::nt::Event>::convert(
const wpi::nt::Event& event) {
return ToString(event, [](const auto& value, wpi::util::raw_ostream& os) {
os << "Event{listener=";
WriteHandle(wpi::nt::Handle{value.listener}, os);
wpi::util::print(os, ", flags={}}}", value.flags);
});
}
std::string Catch::StringMaker<wpi::nt::Handle>::convert(
const wpi::nt::Handle& handle) {
return ToString(handle, WriteHandle);
}
std::string Catch::StringMaker<wpi::nt::net::ClientMessage>::convert(
const wpi::nt::net::ClientMessage& msg) {
return ToString(msg, WriteClientMessage);
}
std::string Catch::StringMaker<wpi::nt::net::ServerMessage>::convert(
const wpi::nt::net::ServerMessage& msg) {
return ToString(msg, WriteServerMessage);
}
std::string Catch::StringMaker<wpi::nt::Value>::convert(
const wpi::nt::Value& value) {
return ToString(value, WriteValue);
}
std::string Catch::StringMaker<wpi::nt::PubSubOptionsImpl>::convert(
const wpi::nt::PubSubOptionsImpl& options) {
return ToString(options, WritePubSubOptions);
}

View File

@@ -4,21 +4,12 @@
#pragma once
#include <ostream>
#include <span>
#include <string>
#include <string_view>
#include <gtest/gtest.h>
#include "wpi/util/TestPrinters.hpp"
#include <catch2/catch_tostring.hpp>
namespace wpi::nt {
namespace net3 {
class Message3;
} // namespace net3
namespace net {
struct ClientMessage;
struct ServerMessage;
@@ -29,12 +20,38 @@ class Handle;
class PubSubOptionsImpl;
class Value;
void PrintTo(const Event& event, std::ostream* os);
void PrintTo(const Handle& handle, std::ostream* os);
void PrintTo(const net3::Message3& msg, std::ostream* os);
void PrintTo(const net::ClientMessage& msg, std::ostream* os);
void PrintTo(const net::ServerMessage& msg, std::ostream* os);
void PrintTo(const Value& value, std::ostream* os);
void PrintTo(const PubSubOptionsImpl& options, std::ostream* os);
} // namespace wpi::nt
namespace Catch {
template <>
struct StringMaker<wpi::nt::Event> {
static std::string convert(const wpi::nt::Event& event);
};
template <>
struct StringMaker<wpi::nt::Handle> {
static std::string convert(const wpi::nt::Handle& handle);
};
template <>
struct StringMaker<wpi::nt::net::ClientMessage> {
static std::string convert(const wpi::nt::net::ClientMessage& msg);
};
template <>
struct StringMaker<wpi::nt::net::ServerMessage> {
static std::string convert(const wpi::nt::net::ServerMessage& msg);
};
template <>
struct StringMaker<wpi::nt::Value> {
static std::string convert(const wpi::nt::Value& value);
};
template <>
struct StringMaker<wpi::nt::PubSubOptionsImpl> {
static std::string convert(const wpi::nt::PubSubOptionsImpl& options);
};
} // namespace Catch

View File

@@ -2,60 +2,63 @@
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "wpi/nt/NetworkTableInstance.hpp"
#include "wpi/nt/NetworkTableListener.hpp"
class TimeSyncTest : public ::testing::Test {
class TimeSyncTest {
public:
TimeSyncTest() : m_inst(wpi::nt::NetworkTableInstance::Create()) {}
~TimeSyncTest() override { wpi::nt::NetworkTableInstance::Destroy(m_inst); }
~TimeSyncTest() { wpi::nt::NetworkTableInstance::Destroy(m_inst); }
protected:
wpi::nt::NetworkTableInstance m_inst;
};
TEST_F(TimeSyncTest, TestLocal) {
TEST_CASE_METHOD(TimeSyncTest, "TimeSyncTest TestLocal",
"[ntcore][time-sync]") {
auto offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);
REQUIRE_FALSE(offset);
}
TEST_F(TimeSyncTest, TestServer) {
TEST_CASE_METHOD(TimeSyncTest, "TimeSyncTest TestServer",
"[ntcore][time-sync]") {
wpi::nt::NetworkTableListenerPoller poller{m_inst};
poller.AddTimeSyncListener(false);
m_inst.StartServer("timesynctest.json", "127.0.0.1", "", 10030);
auto offset = m_inst.GetServerTimeOffset();
ASSERT_TRUE(offset);
ASSERT_EQ(0, *offset);
REQUIRE(offset);
REQUIRE(0 == *offset);
auto events = poller.ReadQueue();
ASSERT_EQ(1u, events.size());
REQUIRE(1u == events.size());
auto data = events[0].GetTimeSyncEventData();
ASSERT_TRUE(data);
ASSERT_TRUE(data->valid);
ASSERT_EQ(0, data->serverTimeOffset);
ASSERT_EQ(0, data->rtt2);
REQUIRE(data);
REQUIRE(data->valid);
REQUIRE(0 == data->serverTimeOffset);
REQUIRE(0 == data->rtt2);
m_inst.StopServer();
offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);
REQUIRE_FALSE(offset);
events = poller.ReadQueue();
ASSERT_EQ(1u, events.size());
REQUIRE(1u == events.size());
data = events[0].GetTimeSyncEventData();
ASSERT_TRUE(data);
ASSERT_FALSE(data->valid);
REQUIRE(data);
REQUIRE_FALSE(data->valid);
}
TEST_F(TimeSyncTest, TestClient) {
TEST_CASE_METHOD(TimeSyncTest, "TimeSyncTest TestClient",
"[ntcore][time-sync]") {
m_inst.StartClient("client");
auto offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);
REQUIRE_FALSE(offset);
m_inst.StopClient();
offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);
REQUIRE_FALSE(offset);
}

View File

@@ -7,15 +7,17 @@
#include <thread>
#include <vector>
#include <gtest/gtest.h>
#include "TestPrinters.hpp"
#include "ValueMatcher.hpp"
#undef FAIL
#undef SUCCEED
#include <catch2/catch_test_macros.hpp>
#include "wpi/nt/ntcore_c.h"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/Synchronization.hpp"
#include "wpi/util/json.hpp"
class TopicListenerTest : public ::testing::Test {
class TopicListenerTest {
public:
TopicListenerTest()
: m_serverInst(wpi::nt::CreateInstance()),
@@ -34,7 +36,7 @@ class TopicListenerTest : public ::testing::Test {
#endif
}
~TopicListenerTest() override {
~TopicListenerTest() {
wpi::nt::DestroyInstance(m_serverInst);
wpi::nt::DestroyInstance(m_clientInst);
}
@@ -62,7 +64,7 @@ void TopicListenerTest::Connect(unsigned int port) {
wpi::nt::EventFlags::CONNECTED);
bool timedOut = false;
if (!wpi::util::WaitForObject(poller, 1.0, &timedOut)) {
FAIL() << "client didn't connect to server";
FAIL("client didn't connect to server");
}
}
@@ -75,16 +77,17 @@ void TopicListenerTest::PublishTopics(NT_Inst inst) {
void TopicListenerTest::CheckEvents(const std::vector<wpi::nt::Event>& events,
NT_Listener handle, unsigned int flags,
std::string_view topicName) {
ASSERT_EQ(events.size(), 1u);
ASSERT_EQ(events[0].listener, handle);
ASSERT_EQ(events[0].flags, flags);
REQUIRE(events.size() == 1u);
REQUIRE(events[0].listener == handle);
REQUIRE(events[0].flags == flags);
auto topicInfo = events[0].GetTopicInfo();
ASSERT_TRUE(topicInfo);
ASSERT_EQ(topicInfo->topic, wpi::nt::GetTopic(m_serverInst, topicName));
ASSERT_EQ(topicInfo->name, topicName);
REQUIRE(topicInfo);
REQUIRE(topicInfo->topic == wpi::nt::GetTopic(m_serverInst, topicName));
REQUIRE(topicInfo->name == topicName);
}
TEST_F(TopicListenerTest, TopicNewLocal) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicNewLocal",
"[ntcore][topic-listener]") {
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
auto handle = wpi::nt::AddPolledListener(
poller, wpi::nt::GetTopic(m_serverInst, "/foo"),
@@ -93,16 +96,14 @@ TEST_F(TopicListenerTest, TopicNewLocal) {
PublishTopics(m_serverInst);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::PUBLISH, "/foo");
}
TEST_F(TopicListenerTest, DISABLED_TopicNewRemote) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicNewRemote",
"[ntcore][topic-listener][.]") {
Connect(10010);
if (HasFatalFailure()) {
return;
}
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
auto handle = wpi::nt::AddPolledListener(
poller, wpi::nt::GetTopic(m_serverInst, "/foo"),
@@ -114,12 +115,13 @@ TEST_F(TopicListenerTest, DISABLED_TopicNewRemote) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::PUBLISH, "/foo");
}
TEST_F(TopicListenerTest, TopicPublishImm) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicPublishImm",
"[ntcore][topic-listener]") {
PublishTopics(m_serverInst);
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
@@ -128,14 +130,15 @@ TEST_F(TopicListenerTest, TopicPublishImm) {
wpi::nt::EventFlags::PUBLISH | wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle,
wpi::nt::EventFlags::PUBLISH | wpi::nt::EventFlags::IMMEDIATE,
"/foo");
}
TEST_F(TopicListenerTest, TopicUnpublishPropsImm) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicUnpublishPropsImm",
"[ntcore][topic-listener]") {
PublishTopics(m_serverInst);
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
@@ -145,12 +148,13 @@ TEST_F(TopicListenerTest, TopicUnpublishPropsImm) {
wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_FALSE(wpi::util::WaitForObject(poller, 0.02, &timedOut));
REQUIRE_FALSE(wpi::util::WaitForObject(poller, 0.02, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
ASSERT_TRUE(events.empty());
REQUIRE(events.empty());
}
TEST_F(TopicListenerTest, TopicUnpublishLocal) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicUnpublishLocal",
"[ntcore][topic-listener]") {
auto topic = wpi::nt::GetTopic(m_serverInst, "/foo");
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
@@ -161,16 +165,14 @@ TEST_F(TopicListenerTest, TopicUnpublishLocal) {
wpi::nt::Unpublish(pub);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::UNPUBLISH, "/foo");
}
TEST_F(TopicListenerTest, DISABLED_TopicUnpublishRemote) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicUnpublishRemote",
"[ntcore][topic-listener][.]") {
Connect(10010);
if (HasFatalFailure()) {
return;
}
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
auto handle = wpi::nt::AddPolledListener(
poller, wpi::nt::GetTopic(m_serverInst, "/foo"),
@@ -187,31 +189,30 @@ TEST_F(TopicListenerTest, DISABLED_TopicUnpublishRemote) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::UNPUBLISH, "/foo");
}
TEST_F(TopicListenerTest, TopicPropertiesLocal) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicPropertiesLocal",
"[ntcore][topic-listener]") {
auto topic = wpi::nt::GetTopic(m_serverInst, "/foo");
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
auto handle = wpi::nt::AddPolledListener(poller, topic,
wpi::nt::EventFlags::PROPERTIES);
wpi::nt::SetTopicProperty(topic, "foo", 5);
wpi::nt::SetTopicProperty(topic, "foo", wpi::util::json{5});
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::PROPERTIES, "/foo");
}
TEST_F(TopicListenerTest, DISABLED_TopicPropertiesRemote) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest TopicPropertiesRemote",
"[ntcore][topic-listener][.]") {
Connect(10010);
if (HasFatalFailure()) {
return;
}
// the topic needs to actually exist
wpi::nt::Publish(wpi::nt::GetTopic(m_serverInst, "/foo"), NT_BOOLEAN,
"boolean");
@@ -222,17 +223,19 @@ TEST_F(TopicListenerTest, DISABLED_TopicPropertiesRemote) {
wpi::nt::EventFlags::PROPERTIES);
wpi::nt::FlushLocal(m_serverInst);
wpi::nt::SetTopicProperty(wpi::nt::GetTopic(m_clientInst, "/foo"), "foo", 5);
wpi::nt::SetTopicProperty(wpi::nt::GetTopic(m_clientInst, "/foo"), "foo",
wpi::util::json{5});
wpi::nt::Flush(m_clientInst);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::PROPERTIES, "/foo");
}
TEST_F(TopicListenerTest, PrefixPublishLocal) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest PrefixPublishLocal",
"[ntcore][topic-listener]") {
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
auto handle = wpi::nt::AddPolledListener(poller, {{"/foo/"}},
wpi::nt::EventFlags::PUBLISH);
@@ -240,16 +243,14 @@ TEST_F(TopicListenerTest, PrefixPublishLocal) {
PublishTopics(m_serverInst);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::PUBLISH);
}
TEST_F(TopicListenerTest, DISABLED_PrefixPublishRemote) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest PrefixPublishRemote",
"[ntcore][topic-listener][.]") {
Connect(10011);
if (HasFatalFailure()) {
return;
}
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
auto handle = wpi::nt::AddPolledListener(poller, {{"/foo/"}},
wpi::nt::EventFlags::PUBLISH);
@@ -260,12 +261,13 @@ TEST_F(TopicListenerTest, DISABLED_PrefixPublishRemote) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle, wpi::nt::EventFlags::PUBLISH);
}
TEST_F(TopicListenerTest, PrefixPublishImm) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest PrefixPublishImm",
"[ntcore][topic-listener]") {
PublishTopics(m_serverInst);
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
@@ -274,13 +276,14 @@ TEST_F(TopicListenerTest, PrefixPublishImm) {
wpi::nt::EventFlags::PUBLISH | wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
CheckEvents(events, handle,
wpi::nt::EventFlags::PUBLISH | wpi::nt::EventFlags::IMMEDIATE);
}
TEST_F(TopicListenerTest, PrefixUnpublishPropsImm) {
TEST_CASE_METHOD(TopicListenerTest, "TopicListenerTest PrefixUnpublishPropsImm",
"[ntcore][topic-listener]") {
PublishTopics(m_serverInst);
auto poller = wpi::nt::CreateListenerPoller(m_serverInst);
@@ -290,7 +293,7 @@ TEST_F(TopicListenerTest, PrefixUnpublishPropsImm) {
wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_FALSE(wpi::util::WaitForObject(poller, 0.02, &timedOut));
REQUIRE_FALSE(wpi::util::WaitForObject(poller, 0.02, &timedOut));
auto events = wpi::nt::ReadListenerQueue(poller);
ASSERT_TRUE(events.empty());
REQUIRE(events.empty());
}

View File

@@ -4,57 +4,60 @@
#include "wpi/nt/Topic.hpp"
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "wpi/nt/NetworkTableInstance.hpp"
class TopicTest : public ::testing::Test {
class TopicTest {
public:
TopicTest() : m_inst{wpi::nt::NetworkTableInstance::Create()} {}
~TopicTest() override { wpi::nt::NetworkTableInstance::Destroy(m_inst); }
~TopicTest() { wpi::nt::NetworkTableInstance::Destroy(m_inst); }
protected:
wpi::nt::NetworkTableInstance m_inst;
};
TEST_F(TopicTest, UserDataDefaultsToNull) {
TEST_CASE_METHOD(TopicTest, "TopicTest UserDataDefaultsToNull",
"[ntcore][topic]") {
auto topic = m_inst.GetTopic("foo");
EXPECT_EQ(nullptr, topic.GetUserData());
CHECK(nullptr == topic.GetUserData());
}
TEST_F(TopicTest, UserDataRoundTrip) {
TEST_CASE_METHOD(TopicTest, "TopicTest UserDataRoundTrip", "[ntcore][topic]") {
auto topic = m_inst.GetTopic("foo");
int data = 5;
topic.SetUserData(&data);
EXPECT_EQ(&data, topic.GetUserData());
CHECK(&data == topic.GetUserData());
}
TEST_F(TopicTest, UserDataCanBeReplacedAndCleared) {
TEST_CASE_METHOD(TopicTest, "TopicTest UserDataCanBeReplacedAndCleared",
"[ntcore][topic]") {
auto topic = m_inst.GetTopic("foo");
auto sameTopic = m_inst.GetTopic("foo");
int data1 = 5;
int data2 = 10;
topic.SetUserData(&data1);
EXPECT_EQ(&data1, sameTopic.GetUserData());
CHECK(&data1 == sameTopic.GetUserData());
sameTopic.SetUserData(&data2);
EXPECT_EQ(&data2, topic.GetUserData());
CHECK(&data2 == topic.GetUserData());
topic.SetUserData(nullptr);
EXPECT_EQ(nullptr, sameTopic.GetUserData());
CHECK(nullptr == sameTopic.GetUserData());
}
TEST_F(TopicTest, UserDataInvalidTopic) {
TEST_CASE_METHOD(TopicTest, "TopicTest UserDataInvalidTopic",
"[ntcore][topic]") {
wpi::nt::Topic topic;
int data = 5;
EXPECT_EQ(nullptr, topic.GetUserData());
CHECK(nullptr == topic.GetUserData());
topic.SetUserData(&data);
EXPECT_EQ(nullptr, topic.GetUserData());
CHECK(nullptr == topic.GetUserData());
}

View File

@@ -2,33 +2,30 @@
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <gtest/gtest.h>
#include "TestPrinters.hpp"
#include "ValueMatcher.hpp"
#undef FAIL
#undef SUCCEED
#include <catch2/catch_test_macros.hpp>
#include "wpi/nt/ntcore_c.h"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/Synchronization.hpp"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::IsNull;
using ::testing::Return;
namespace wpi::nt {
// Test only local here; it's more reliable to mock the network
class ValueListenerTest : public ::testing::Test {
class ValueListenerTest {
public:
ValueListenerTest() : m_inst{wpi::nt::CreateInstance()} {}
~ValueListenerTest() override { wpi::nt::DestroyInstance(m_inst); }
~ValueListenerTest() { wpi::nt::DestroyInstance(m_inst); }
protected:
NT_Inst m_inst;
};
TEST_F(ValueListenerTest, MultiPollSub) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest MultiPollSub",
"[ntcore][value-listener]") {
auto topic = wpi::nt::GetTopic(m_inst, "foo");
auto pub = wpi::nt::Publish(topic, NT_DOUBLE, "double");
auto sub = wpi::nt::Subscribe(topic, NT_DOUBLE, "double");
@@ -46,45 +43,46 @@ TEST_F(ValueListenerTest, MultiPollSub) {
wpi::nt::SetDouble(pub, 0);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller1, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
ASSERT_TRUE(wpi::util::WaitForObject(poller2, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
ASSERT_TRUE(wpi::util::WaitForObject(poller3, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller1, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller2, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller3, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results1 = wpi::nt::ReadListenerQueue(poller1);
auto results2 = wpi::nt::ReadListenerQueue(poller2);
auto results3 = wpi::nt::ReadListenerQueue(poller3);
ASSERT_EQ(results1.size(), 1u);
EXPECT_EQ(results1[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results1[0].listener, h1);
REQUIRE(results1.size() == 1u);
CHECK(results1[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results1[0].listener == h1);
auto valueData = results1[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
ASSERT_EQ(results2.size(), 1u);
EXPECT_EQ(results2[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results2[0].listener, h2);
REQUIRE(results2.size() == 1u);
CHECK(results2[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results2[0].listener == h2);
valueData = results2[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
ASSERT_EQ(results3.size(), 1u);
EXPECT_EQ(results3[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results3[0].listener, h3);
REQUIRE(results3.size() == 1u);
CHECK(results3[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results3[0].listener == h3);
valueData = results3[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, PollMultiSub) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollMultiSub",
"[ntcore][value-listener]") {
auto topic = wpi::nt::GetTopic(m_inst, "foo");
auto pub = wpi::nt::Publish(topic, NT_DOUBLE, "double");
auto sub1 = wpi::nt::Subscribe(topic, NT_DOUBLE, "double");
@@ -99,29 +97,30 @@ TEST_F(ValueListenerTest, PollMultiSub) {
wpi::nt::SetDouble(pub, 0);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h1);
REQUIRE(results.size() == 2u);
CHECK(results[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h1);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub1);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub1);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
EXPECT_EQ(results[1].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[1].listener, h2);
CHECK(results[1].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[1].listener == h2);
valueData = results[1].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub2);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub2);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, PollMultiSubTopic) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollMultiSubTopic",
"[ntcore][value-listener]") {
auto topic1 = wpi::nt::GetTopic(m_inst, "foo");
auto topic2 = wpi::nt::GetTopic(m_inst, "bar");
auto pub1 = wpi::nt::Publish(topic1, NT_DOUBLE, "double");
@@ -139,29 +138,30 @@ TEST_F(ValueListenerTest, PollMultiSubTopic) {
wpi::nt::SetDouble(pub2, 1);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h1);
REQUIRE(results.size() == 2u);
CHECK(results[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h1);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub1);
EXPECT_EQ(valueData->topic, topic1);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub1);
CHECK(valueData->topic == topic1);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
EXPECT_EQ(results[1].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[1].listener, h2);
CHECK(results[1].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[1].listener == h2);
valueData = results[1].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub2);
EXPECT_EQ(valueData->topic, topic2);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(1.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub2);
CHECK(valueData->topic == topic2);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(1.0));
}
TEST_F(ValueListenerTest, PollSubMultiple) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollSubMultiple",
"[ntcore][value-listener]") {
auto topic1 = wpi::nt::GetTopic(m_inst, "foo/1");
auto topic2 = wpi::nt::GetTopic(m_inst, "foo/2");
auto pub1 = wpi::nt::Publish(topic1, NT_DOUBLE, "double");
@@ -176,29 +176,30 @@ TEST_F(ValueListenerTest, PollSubMultiple) {
wpi::nt::SetDouble(pub2, 1);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 2u);
CHECK(results[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic1);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic1);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
EXPECT_EQ(results[1].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[1].listener, h);
CHECK(results[1].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[1].listener == h);
valueData = results[1].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic2);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(1.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic2);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(1.0));
}
TEST_F(ValueListenerTest, PollSubPrefixCreated) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollSubPrefixCreated",
"[ntcore][value-listener]") {
auto poller = wpi::nt::CreateListenerPoller(m_inst);
auto h = wpi::nt::AddPolledListener(poller, {{"foo"}},
wpi::nt::EventFlags::VALUE_LOCAL);
@@ -215,53 +216,55 @@ TEST_F(ValueListenerTest, PollSubPrefixCreated) {
wpi::nt::SetDouble(pub3, 1);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 2u);
CHECK(results[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->topic, topic1);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->topic == topic1);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
EXPECT_EQ(results[1].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[1].listener, h);
CHECK(results[1].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[1].listener == h);
valueData = results[1].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->topic, topic2);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(1.0));
REQUIRE(valueData);
CHECK(valueData->topic == topic2);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(1.0));
}
TEST_F(ValueListenerTest, PollEntry) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollEntry",
"[ntcore][value-listener]") {
auto entry = wpi::nt::GetEntry(m_inst, "foo");
auto poller = wpi::nt::CreateListenerPoller(m_inst);
auto h = wpi::nt::AddPolledListener(poller, entry,
wpi::nt::EventFlags::VALUE_LOCAL);
ASSERT_TRUE(wpi::nt::SetDouble(entry, 0));
REQUIRE(wpi::nt::SetDouble(entry, 0));
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 1u);
CHECK(results[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, entry);
EXPECT_EQ(valueData->topic, wpi::nt::GetTopic(m_inst, "foo"));
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == entry);
CHECK(valueData->topic == wpi::nt::GetTopic(m_inst, "foo"));
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, PollImmediate) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollImmediate",
"[ntcore][value-listener]") {
auto entry = wpi::nt::GetEntry(m_inst, "foo");
ASSERT_TRUE(wpi::nt::SetDouble(entry, 0));
REQUIRE(wpi::nt::SetDouble(entry, 0));
auto poller = wpi::nt::CreateListenerPoller(m_inst);
auto h = wpi::nt::AddPolledListener(
@@ -269,23 +272,24 @@ TEST_F(ValueListenerTest, PollImmediate) {
wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags & (wpi::nt::EventFlags::VALUE_LOCAL |
wpi::nt::EventFlags::IMMEDIATE),
wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 1u);
CHECK((results[0].flags &
(wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE)) ==
(wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE));
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, entry);
EXPECT_EQ(valueData->topic, wpi::nt::GetTopic(m_inst, "foo"));
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == entry);
CHECK(valueData->topic == wpi::nt::GetTopic(m_inst, "foo"));
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, PollImmediateNoValue) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest PollImmediateNoValue",
"[ntcore][value-listener]") {
auto entry = wpi::nt::GetEntry(m_inst, "foo");
auto poller = wpi::nt::CreateListenerPoller(m_inst);
@@ -294,29 +298,31 @@ TEST_F(ValueListenerTest, PollImmediateNoValue) {
wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_FALSE(wpi::util::WaitForObject(poller, 0.02, &timedOut));
ASSERT_TRUE(timedOut);
REQUIRE_FALSE(wpi::util::WaitForObject(poller, 0.02, &timedOut));
REQUIRE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_TRUE(results.empty());
REQUIRE(results.empty());
// now set a value
ASSERT_TRUE(wpi::nt::SetDouble(entry, 0));
REQUIRE(wpi::nt::SetDouble(entry, 0));
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
results = wpi::nt::ReadListenerQueue(poller);
ASSERT_FALSE(timedOut);
REQUIRE_FALSE(timedOut);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags, wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 1u);
CHECK(results[0].flags == wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, entry);
EXPECT_EQ(valueData->topic, wpi::nt::GetTopic(m_inst, "foo"));
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == entry);
CHECK(valueData->topic == wpi::nt::GetTopic(m_inst, "foo"));
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, PollImmediateSubMultiple) {
TEST_CASE_METHOD(ValueListenerTest,
"ValueListenerTest PollImmediateSubMultiple",
"[ntcore][value-listener]") {
auto topic1 = wpi::nt::GetTopic(m_inst, "foo/1");
auto topic2 = wpi::nt::GetTopic(m_inst, "foo/2");
auto pub1 = wpi::nt::Publish(topic1, NT_DOUBLE, "double");
@@ -331,33 +337,34 @@ TEST_F(ValueListenerTest, PollImmediateSubMultiple) {
wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].flags & (wpi::nt::EventFlags::VALUE_LOCAL |
wpi::nt::EventFlags::IMMEDIATE),
wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 2u);
CHECK((results[0].flags &
(wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE)) ==
(wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE));
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic1);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic1);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
EXPECT_EQ(results[1].flags & (wpi::nt::EventFlags::VALUE_LOCAL |
wpi::nt::EventFlags::IMMEDIATE),
wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE);
EXPECT_EQ(results[1].listener, h);
CHECK((results[1].flags &
(wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE)) ==
(wpi::nt::EventFlags::VALUE_LOCAL | wpi::nt::EventFlags::IMMEDIATE));
CHECK(results[1].listener == h);
valueData = results[1].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub);
EXPECT_EQ(valueData->topic, topic2);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(1.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub);
CHECK(valueData->topic == topic2);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(1.0));
}
TEST_F(ValueListenerTest, TwoSubOneListener) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest TwoSubOneListener",
"[ntcore][value-listener]") {
auto topic = wpi::nt::GetTopic(m_inst, "foo");
auto pub = wpi::nt::Publish(topic, NT_DOUBLE, "double");
auto sub1 = wpi::nt::Subscribe(topic, NT_DOUBLE, "double");
@@ -373,22 +380,23 @@ TEST_F(ValueListenerTest, TwoSubOneListener) {
wpi::nt::SetDouble(pub, 0);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags & wpi::nt::EventFlags::VALUE_LOCAL,
wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 1u);
CHECK((results[0].flags & wpi::nt::EventFlags::VALUE_LOCAL) ==
wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub1);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub1);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, TwoSubOneMultiListener) {
TEST_CASE_METHOD(ValueListenerTest, "ValueListenerTest TwoSubOneMultiListener",
"[ntcore][value-listener]") {
auto topic = wpi::nt::GetTopic(m_inst, "foo");
auto pub = wpi::nt::Publish(topic, NT_DOUBLE, "double");
auto sub1 = wpi::nt::Subscribe(topic, NT_DOUBLE, "double");
@@ -404,19 +412,19 @@ TEST_F(ValueListenerTest, TwoSubOneMultiListener) {
wpi::nt::SetDouble(pub, 0);
bool timedOut = false;
ASSERT_TRUE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
REQUIRE(wpi::util::WaitForObject(poller, 1.0, &timedOut));
REQUIRE_FALSE(timedOut);
auto results = wpi::nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags & wpi::nt::EventFlags::VALUE_LOCAL,
wpi::nt::EventFlags::VALUE_LOCAL);
EXPECT_EQ(results[0].listener, h);
REQUIRE(results.size() == 1u);
CHECK((results[0].flags & wpi::nt::EventFlags::VALUE_LOCAL) ==
wpi::nt::EventFlags::VALUE_LOCAL);
CHECK(results[0].listener == h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub3);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, wpi::nt::Value::MakeDouble(0.0));
REQUIRE(valueData);
CHECK(valueData->subentry == sub3);
CHECK(valueData->topic == topic);
CHECK(valueData->value == wpi::nt::Value::MakeDouble(0.0));
}
} // namespace wpi::nt

View File

@@ -1,29 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ValueMatcher.hpp"
#include "TestPrinters.hpp"
namespace wpi::nt {
bool ValueMatcher::MatchAndExplain(
Value val, ::testing::MatchResultListener* listener) const {
if ((!val && goodval) || (val && !goodval) ||
(val && goodval && val != goodval)) {
return false;
}
return true;
}
void ValueMatcher::DescribeTo(::std::ostream* os) const {
PrintTo(goodval, os);
}
void ValueMatcher::DescribeNegationTo(::std::ostream* os) const {
*os << "is not equal to ";
PrintTo(goodval, os);
}
} // namespace wpi::nt

View File

@@ -1,33 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <memory>
#include <ostream>
#include <utility>
#include "gmock/gmock.h"
#include "wpi/nt/NetworkTableValue.hpp"
namespace wpi::nt {
class ValueMatcher : public ::testing::MatcherInterface<Value> {
public:
explicit ValueMatcher(Value goodval_) : goodval(std::move(goodval_)) {}
bool MatchAndExplain(Value msg,
::testing::MatchResultListener* listener) const override;
void DescribeTo(::std::ostream* os) const override;
void DescribeNegationTo(::std::ostream* os) const override;
private:
Value goodval;
};
inline ::testing::Matcher<Value> ValueEq(const Value& goodval) {
return ::testing::MakeMatcher(new ValueMatcher(goodval));
}
} // namespace wpi::nt

View File

@@ -8,7 +8,7 @@
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "TestPrinters.hpp"
#include "Value_internal.hpp"
@@ -29,210 +29,208 @@ inline bool operator==(std::span<T> lhs, std::span<U> rhs) {
namespace wpi::nt {
class ValueTest : public ::testing::Test {};
using ValueDeathTest = ValueTest;
TEST_F(ValueTest, ConstructEmpty) {
TEST_CASE("ValueTest ConstructEmpty", "[ntcore][value]") {
Value v;
ASSERT_EQ(NT_UNASSIGNED, v.type());
REQUIRE(NT_UNASSIGNED == v.type());
}
TEST_F(ValueTest, Boolean) {
TEST_CASE("ValueTest Boolean", "[ntcore][value]") {
auto v = Value::MakeBoolean(false);
ASSERT_EQ(NT_BOOLEAN, v.type());
ASSERT_FALSE(v.GetBoolean());
REQUIRE(NT_BOOLEAN == v.type());
REQUIRE_FALSE(v.GetBoolean());
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_BOOLEAN, cv.type);
ASSERT_EQ(0, cv.data.v_boolean);
REQUIRE(NT_BOOLEAN == cv.type);
REQUIRE(0 == cv.data.v_boolean);
v = Value::MakeBoolean(true);
ASSERT_EQ(NT_BOOLEAN, v.type());
ASSERT_TRUE(v.GetBoolean());
REQUIRE(NT_BOOLEAN == v.type());
REQUIRE(v.GetBoolean());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_BOOLEAN, cv.type);
ASSERT_EQ(1, cv.data.v_boolean);
REQUIRE(NT_BOOLEAN == cv.type);
REQUIRE(1 == cv.data.v_boolean);
NT_DisposeValue(&cv);
}
TEST_F(ValueTest, Double) {
TEST_CASE("ValueTest Double", "[ntcore][value]") {
auto v = Value::MakeDouble(0.5);
ASSERT_EQ(NT_DOUBLE, v.type());
ASSERT_EQ(0.5, v.GetDouble());
REQUIRE(NT_DOUBLE == v.type());
REQUIRE(0.5 == v.GetDouble());
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_DOUBLE, cv.type);
ASSERT_EQ(0.5, cv.data.v_double);
REQUIRE(NT_DOUBLE == cv.type);
REQUIRE(0.5 == cv.data.v_double);
v = Value::MakeDouble(0.25);
ASSERT_EQ(NT_DOUBLE, v.type());
ASSERT_EQ(0.25, v.GetDouble());
REQUIRE(NT_DOUBLE == v.type());
REQUIRE(0.25 == v.GetDouble());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_DOUBLE, cv.type);
ASSERT_EQ(0.25, cv.data.v_double);
REQUIRE(NT_DOUBLE == cv.type);
REQUIRE(0.25 == cv.data.v_double);
NT_DisposeValue(&cv);
}
TEST_F(ValueTest, String) {
TEST_CASE("ValueTest String", "[ntcore][value]") {
auto v = Value::MakeString("hello");
ASSERT_EQ(NT_STRING, v.type());
ASSERT_EQ("hello", v.GetString());
REQUIRE(NT_STRING == v.type());
REQUIRE("hello" == v.GetString());
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_STRING, cv.type);
ASSERT_EQ("hello"sv, wpi::util::to_string_view(&cv.data.v_string));
ASSERT_EQ(5u, cv.data.v_string.len);
REQUIRE(NT_STRING == cv.type);
REQUIRE("hello"sv == wpi::util::to_string_view(&cv.data.v_string));
REQUIRE(5u == cv.data.v_string.len);
v = Value::MakeString("goodbye");
ASSERT_EQ(NT_STRING, v.type());
ASSERT_EQ("goodbye", v.GetString());
REQUIRE(NT_STRING == v.type());
REQUIRE("goodbye" == v.GetString());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_STRING, cv.type);
ASSERT_EQ("goodbye"sv, wpi::util::to_string_view(&cv.data.v_string));
ASSERT_EQ(7u, cv.data.v_string.len);
REQUIRE(NT_STRING == cv.type);
REQUIRE("goodbye"sv == wpi::util::to_string_view(&cv.data.v_string));
REQUIRE(7u == cv.data.v_string.len);
NT_DisposeValue(&cv);
}
TEST_F(ValueTest, Raw) {
TEST_CASE("ValueTest Raw", "[ntcore][value]") {
std::vector<uint8_t> arr{5, 4, 3, 2, 1};
auto v = Value::MakeRaw(arr);
ASSERT_EQ(NT_RAW, v.type());
ASSERT_EQ(std::span<const uint8_t>(arr), v.GetRaw());
REQUIRE(NT_RAW == v.type());
REQUIRE(std::span<const uint8_t>(arr) == v.GetRaw());
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_RAW, cv.type);
ASSERT_EQ(5u, cv.data.v_raw.size);
ASSERT_EQ(std::span(reinterpret_cast<const uint8_t*>("\5\4\3\2\1"), 5),
std::span(cv.data.v_raw.data, 5));
REQUIRE(NT_RAW == cv.type);
REQUIRE(5u == cv.data.v_raw.size);
REQUIRE(std::span(reinterpret_cast<const uint8_t*>("\5\4\3\2\1"), 5) ==
std::span(cv.data.v_raw.data, 5));
std::vector<uint8_t> arr2{1, 2, 3, 4, 5, 6};
v = Value::MakeRaw(arr2);
ASSERT_EQ(NT_RAW, v.type());
ASSERT_EQ(std::span<const uint8_t>(arr2), v.GetRaw());
REQUIRE(NT_RAW == v.type());
REQUIRE(std::span<const uint8_t>(arr2) == v.GetRaw());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_RAW, cv.type);
ASSERT_EQ(6u, cv.data.v_raw.size);
ASSERT_EQ(std::span(reinterpret_cast<const uint8_t*>("\1\2\3\4\5\6"), 6),
std::span(cv.data.v_raw.data, 6));
REQUIRE(NT_RAW == cv.type);
REQUIRE(6u == cv.data.v_raw.size);
REQUIRE(std::span(reinterpret_cast<const uint8_t*>("\1\2\3\4\5\6"), 6) ==
std::span(cv.data.v_raw.data, 6));
NT_DisposeValue(&cv);
}
TEST_F(ValueTest, BooleanArray) {
TEST_CASE("ValueTest BooleanArray", "[ntcore][value]") {
std::vector<int> vec{1, 0, 1};
auto v = Value::MakeBooleanArray(vec);
ASSERT_EQ(NT_BOOLEAN_ARRAY, v.type());
ASSERT_EQ(std::span<const int>(vec), v.GetBooleanArray());
REQUIRE(NT_BOOLEAN_ARRAY == v.type());
REQUIRE(std::span<const int>(vec) == v.GetBooleanArray());
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_BOOLEAN_ARRAY, cv.type);
ASSERT_EQ(3u, cv.data.arr_boolean.size);
ASSERT_EQ(vec[0], cv.data.arr_boolean.arr[0]);
ASSERT_EQ(vec[1], cv.data.arr_boolean.arr[1]);
ASSERT_EQ(vec[2], cv.data.arr_boolean.arr[2]);
REQUIRE(NT_BOOLEAN_ARRAY == cv.type);
REQUIRE(3u == cv.data.arr_boolean.size);
REQUIRE(vec[0] == cv.data.arr_boolean.arr[0]);
REQUIRE(vec[1] == cv.data.arr_boolean.arr[1]);
REQUIRE(vec[2] == cv.data.arr_boolean.arr[2]);
// assign with same size
vec = {0, 1, 0};
v = Value::MakeBooleanArray(vec);
ASSERT_EQ(NT_BOOLEAN_ARRAY, v.type());
ASSERT_EQ(std::span<const int>(vec), v.GetBooleanArray());
REQUIRE(NT_BOOLEAN_ARRAY == v.type());
REQUIRE(std::span<const int>(vec) == v.GetBooleanArray());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_BOOLEAN_ARRAY, cv.type);
ASSERT_EQ(3u, cv.data.arr_boolean.size);
ASSERT_EQ(vec[0], cv.data.arr_boolean.arr[0]);
ASSERT_EQ(vec[1], cv.data.arr_boolean.arr[1]);
ASSERT_EQ(vec[2], cv.data.arr_boolean.arr[2]);
REQUIRE(NT_BOOLEAN_ARRAY == cv.type);
REQUIRE(3u == cv.data.arr_boolean.size);
REQUIRE(vec[0] == cv.data.arr_boolean.arr[0]);
REQUIRE(vec[1] == cv.data.arr_boolean.arr[1]);
REQUIRE(vec[2] == cv.data.arr_boolean.arr[2]);
// assign with different size
vec = {1, 0};
v = Value::MakeBooleanArray(vec);
ASSERT_EQ(NT_BOOLEAN_ARRAY, v.type());
ASSERT_EQ(std::span<const int>(vec), v.GetBooleanArray());
REQUIRE(NT_BOOLEAN_ARRAY == v.type());
REQUIRE(std::span<const int>(vec) == v.GetBooleanArray());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_BOOLEAN_ARRAY, cv.type);
ASSERT_EQ(2u, cv.data.arr_boolean.size);
ASSERT_EQ(vec[0], cv.data.arr_boolean.arr[0]);
ASSERT_EQ(vec[1], cv.data.arr_boolean.arr[1]);
REQUIRE(NT_BOOLEAN_ARRAY == cv.type);
REQUIRE(2u == cv.data.arr_boolean.size);
REQUIRE(vec[0] == cv.data.arr_boolean.arr[0]);
REQUIRE(vec[1] == cv.data.arr_boolean.arr[1]);
NT_DisposeValue(&cv);
}
TEST_F(ValueTest, DoubleArray) {
TEST_CASE("ValueTest DoubleArray", "[ntcore][value]") {
std::vector<double> vec{0.5, 0.25, 0.5};
auto v = Value::MakeDoubleArray(vec);
ASSERT_EQ(NT_DOUBLE_ARRAY, v.type());
ASSERT_EQ(std::span<const double>(vec), v.GetDoubleArray());
REQUIRE(NT_DOUBLE_ARRAY == v.type());
REQUIRE(std::span<const double>(vec) == v.GetDoubleArray());
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_DOUBLE_ARRAY, cv.type);
ASSERT_EQ(3u, cv.data.arr_double.size);
ASSERT_EQ(vec[0], cv.data.arr_double.arr[0]);
ASSERT_EQ(vec[1], cv.data.arr_double.arr[1]);
ASSERT_EQ(vec[2], cv.data.arr_double.arr[2]);
REQUIRE(NT_DOUBLE_ARRAY == cv.type);
REQUIRE(3u == cv.data.arr_double.size);
REQUIRE(vec[0] == cv.data.arr_double.arr[0]);
REQUIRE(vec[1] == cv.data.arr_double.arr[1]);
REQUIRE(vec[2] == cv.data.arr_double.arr[2]);
// assign with same size
vec = {0.25, 0.5, 0.25};
v = Value::MakeDoubleArray(vec);
ASSERT_EQ(NT_DOUBLE_ARRAY, v.type());
ASSERT_EQ(std::span<const double>(vec), v.GetDoubleArray());
REQUIRE(NT_DOUBLE_ARRAY == v.type());
REQUIRE(std::span<const double>(vec) == v.GetDoubleArray());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_DOUBLE_ARRAY, cv.type);
ASSERT_EQ(3u, cv.data.arr_double.size);
ASSERT_EQ(vec[0], cv.data.arr_double.arr[0]);
ASSERT_EQ(vec[1], cv.data.arr_double.arr[1]);
ASSERT_EQ(vec[2], cv.data.arr_double.arr[2]);
REQUIRE(NT_DOUBLE_ARRAY == cv.type);
REQUIRE(3u == cv.data.arr_double.size);
REQUIRE(vec[0] == cv.data.arr_double.arr[0]);
REQUIRE(vec[1] == cv.data.arr_double.arr[1]);
REQUIRE(vec[2] == cv.data.arr_double.arr[2]);
// assign with different size
vec = {0.5, 0.25};
v = Value::MakeDoubleArray(vec);
ASSERT_EQ(NT_DOUBLE_ARRAY, v.type());
ASSERT_EQ(std::span<const double>(vec), v.GetDoubleArray());
REQUIRE(NT_DOUBLE_ARRAY == v.type());
REQUIRE(std::span<const double>(vec) == v.GetDoubleArray());
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_DOUBLE_ARRAY, cv.type);
ASSERT_EQ(2u, cv.data.arr_double.size);
ASSERT_EQ(vec[0], cv.data.arr_double.arr[0]);
ASSERT_EQ(vec[1], cv.data.arr_double.arr[1]);
REQUIRE(NT_DOUBLE_ARRAY == cv.type);
REQUIRE(2u == cv.data.arr_double.size);
REQUIRE(vec[0] == cv.data.arr_double.arr[0]);
REQUIRE(vec[1] == cv.data.arr_double.arr[1]);
NT_DisposeValue(&cv);
}
TEST_F(ValueTest, StringArray) {
TEST_CASE("ValueTest StringArray", "[ntcore][value]") {
std::vector<std::string> vec;
vec.push_back("hello");
vec.push_back("goodbye");
vec.push_back("string");
auto v = Value::MakeStringArray(std::move(vec));
ASSERT_EQ(NT_STRING_ARRAY, v.type());
ASSERT_EQ(3u, v.GetStringArray().size());
ASSERT_EQ("hello"sv, v.GetStringArray()[0]);
ASSERT_EQ("goodbye"sv, v.GetStringArray()[1]);
ASSERT_EQ("string"sv, v.GetStringArray()[2]);
REQUIRE(NT_STRING_ARRAY == v.type());
REQUIRE(3u == v.GetStringArray().size());
REQUIRE("hello"sv == v.GetStringArray()[0]);
REQUIRE("goodbye"sv == v.GetStringArray()[1]);
REQUIRE("string"sv == v.GetStringArray()[2]);
NT_Value cv;
NT_InitValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_STRING_ARRAY, cv.type);
ASSERT_EQ(3u, cv.data.arr_string.size);
ASSERT_EQ("hello"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[0]));
ASSERT_EQ("goodbye"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[1]));
ASSERT_EQ("string"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[2]));
REQUIRE(NT_STRING_ARRAY == cv.type);
REQUIRE(3u == cv.data.arr_string.size);
REQUIRE("hello"sv == wpi::util::to_string_view(&cv.data.arr_string.arr[0]));
REQUIRE("goodbye"sv ==
(wpi::util::to_string_view(&cv.data.arr_string.arr[1])));
REQUIRE("string"sv ==
(wpi::util::to_string_view(&cv.data.arr_string.arr[2])));
// assign with same size
vec.clear();
@@ -240,188 +238,171 @@ TEST_F(ValueTest, StringArray) {
vec.push_back("str2");
vec.push_back("string3");
v = Value::MakeStringArray(vec);
ASSERT_EQ(NT_STRING_ARRAY, v.type());
ASSERT_EQ(3u, v.GetStringArray().size());
ASSERT_EQ("s1"sv, v.GetStringArray()[0]);
ASSERT_EQ("str2"sv, v.GetStringArray()[1]);
ASSERT_EQ("string3"sv, v.GetStringArray()[2]);
REQUIRE(NT_STRING_ARRAY == v.type());
REQUIRE(3u == v.GetStringArray().size());
REQUIRE("s1"sv == v.GetStringArray()[0]);
REQUIRE("str2"sv == v.GetStringArray()[1]);
REQUIRE("string3"sv == v.GetStringArray()[2]);
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_STRING_ARRAY, cv.type);
ASSERT_EQ(3u, cv.data.arr_string.size);
ASSERT_EQ("s1"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[0]));
ASSERT_EQ("str2"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[1]));
ASSERT_EQ("string3"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[2]));
REQUIRE(NT_STRING_ARRAY == cv.type);
REQUIRE(3u == cv.data.arr_string.size);
REQUIRE("s1"sv == wpi::util::to_string_view(&cv.data.arr_string.arr[0]));
REQUIRE("str2"sv == wpi::util::to_string_view(&cv.data.arr_string.arr[1]));
REQUIRE("string3"sv ==
(wpi::util::to_string_view(&cv.data.arr_string.arr[2])));
// assign with different size
vec.clear();
vec.push_back("short");
vec.push_back("er");
v = Value::MakeStringArray(std::move(vec));
ASSERT_EQ(NT_STRING_ARRAY, v.type());
ASSERT_EQ(2u, v.GetStringArray().size());
ASSERT_EQ("short"sv, v.GetStringArray()[0]);
ASSERT_EQ("er"sv, v.GetStringArray()[1]);
REQUIRE(NT_STRING_ARRAY == v.type());
REQUIRE(2u == v.GetStringArray().size());
REQUIRE("short"sv == v.GetStringArray()[0]);
REQUIRE("er"sv == v.GetStringArray()[1]);
NT_DisposeValue(&cv);
ConvertToC(v, &cv);
ASSERT_EQ(NT_STRING_ARRAY, cv.type);
ASSERT_EQ(2u, cv.data.arr_string.size);
ASSERT_EQ("short"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[0]));
ASSERT_EQ("er"sv, wpi::util::to_string_view(&cv.data.arr_string.arr[1]));
REQUIRE(NT_STRING_ARRAY == cv.type);
REQUIRE(2u == cv.data.arr_string.size);
REQUIRE("short"sv == wpi::util::to_string_view(&cv.data.arr_string.arr[0]));
REQUIRE("er"sv == wpi::util::to_string_view(&cv.data.arr_string.arr[1]));
NT_DisposeValue(&cv);
}
// Google Test doesn't have ASSERT_DEATH when compiled with emscripten
#ifndef __EMSCRIPTEN__
#ifdef NDEBUG
TEST_F(ValueDeathTest, DISABLED_GetAssertions) {
#else
TEST_F(ValueDeathTest, DISABLED_GetAssertions) {
#endif
Value v;
ASSERT_DEATH((void)v.GetBoolean(), "type == NT_BOOLEAN");
ASSERT_DEATH((void)v.GetDouble(), "type == NT_DOUBLE");
ASSERT_DEATH((void)v.GetString(), "type == NT_STRING");
ASSERT_DEATH((void)v.GetRaw(), "type == NT_RAW");
ASSERT_DEATH((void)v.GetBooleanArray(), "type == NT_BOOLEAN_ARRAY");
ASSERT_DEATH((void)v.GetDoubleArray(), "type == NT_DOUBLE_ARRAY");
ASSERT_DEATH((void)v.GetStringArray(), "type == NT_STRING_ARRAY");
}
#endif
TEST_F(ValueTest, UnassignedComparison) {
TEST_CASE("ValueTest UnassignedComparison", "[ntcore][value]") {
Value v1, v2;
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
}
TEST_F(ValueTest, MixedComparison) {
TEST_CASE("ValueTest MixedComparison", "[ntcore][value]") {
Value v1;
auto v2 = Value::MakeBoolean(true);
ASSERT_NE(v1, v2); // unassigned vs boolean
REQUIRE(v1 != v2); // unassigned vs boolean
auto v3 = Value::MakeDouble(0.5);
ASSERT_NE(v2, v3); // boolean vs double
REQUIRE(v2 != v3); // boolean vs double
}
TEST_F(ValueTest, BooleanComparison) {
TEST_CASE("ValueTest BooleanComparison", "[ntcore][value]") {
auto v1 = Value::MakeBoolean(true);
auto v2 = Value::MakeBoolean(true);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
v2 = Value::MakeBoolean(false);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
}
TEST_F(ValueTest, DoubleComparison) {
TEST_CASE("ValueTest DoubleComparison", "[ntcore][value]") {
auto v1 = Value::MakeDouble(0.25);
auto v2 = Value::MakeDouble(0.25);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
v2 = Value::MakeDouble(0.5);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
}
TEST_F(ValueTest, StringComparison) {
TEST_CASE("ValueTest StringComparison", "[ntcore][value]") {
auto v1 = Value::MakeString("hello");
auto v2 = Value::MakeString("hello");
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
v2 = Value::MakeString("world"); // different contents
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
v2 = Value::MakeString("goodbye"); // different size
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
}
TEST_F(ValueTest, BooleanArrayComparison) {
TEST_CASE("ValueTest BooleanArrayComparison", "[ntcore][value]") {
std::vector<int> vec{1, 0, 1};
auto v1 = Value::MakeBooleanArray(vec);
auto v2 = Value::MakeBooleanArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
// different contents
vec = {1, 1, 1};
v2 = Value::MakeBooleanArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// different size
vec = {1, 0};
v2 = Value::MakeBooleanArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// empty
vec = {};
v1 = Value::MakeBooleanArray(vec);
v2 = Value::MakeBooleanArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
}
TEST_F(ValueTest, IntegerArrayComparison) {
TEST_CASE("ValueTest IntegerArrayComparison", "[ntcore][value]") {
std::vector<int64_t> vec{-42, 0, 1};
auto v1 = Value::MakeIntegerArray(vec);
auto v2 = Value::MakeIntegerArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
// different contents
vec = {-42, 1, 1};
v2 = Value::MakeIntegerArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// different size
vec = {-42, 0};
v2 = Value::MakeIntegerArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// empty
vec = {};
v1 = Value::MakeIntegerArray(vec);
v2 = Value::MakeIntegerArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
}
TEST_F(ValueTest, FloatArrayComparison) {
TEST_CASE("ValueTest FloatArrayComparison", "[ntcore][value]") {
std::vector<float> vec{0.5, 0.25, 0.5};
auto v1 = Value::MakeFloatArray(vec);
auto v2 = Value::MakeFloatArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
// different contents
vec = {0.5, 0.5, 0.5};
v2 = Value::MakeFloatArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// different size
vec = {0.5, 0.25};
v2 = Value::MakeFloatArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// empty
vec = {};
v1 = Value::MakeFloatArray(vec);
v2 = Value::MakeFloatArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
}
TEST_F(ValueTest, DoubleArrayComparison) {
TEST_CASE("ValueTest DoubleArrayComparison", "[ntcore][value]") {
std::vector<double> vec{0.5, 0.25, 0.5};
auto v1 = Value::MakeDoubleArray(vec);
auto v2 = Value::MakeDoubleArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
// different contents
vec = {0.5, 0.5, 0.5};
v2 = Value::MakeDoubleArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// different size
vec = {0.5, 0.25};
v2 = Value::MakeDoubleArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// empty
vec = {};
v1 = Value::MakeDoubleArray(vec);
v2 = Value::MakeDoubleArray(vec);
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
}
TEST_F(ValueTest, StringArrayComparison) {
TEST_CASE("ValueTest StringArrayComparison", "[ntcore][value]") {
std::vector<std::string> vec;
vec.push_back("hello");
vec.push_back("goodbye");
@@ -432,7 +413,7 @@ TEST_F(ValueTest, StringArrayComparison) {
vec.push_back("goodbye");
vec.push_back("string");
auto v2 = Value::MakeStringArray(std::move(vec));
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
// different contents
vec.clear();
@@ -440,7 +421,7 @@ TEST_F(ValueTest, StringArrayComparison) {
vec.push_back("goodby2");
vec.push_back("string");
v2 = Value::MakeStringArray(std::move(vec));
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// different sized contents
vec.clear();
@@ -448,20 +429,20 @@ TEST_F(ValueTest, StringArrayComparison) {
vec.push_back("goodbye2");
vec.push_back("string");
v2 = Value::MakeStringArray(vec);
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// different size
vec.clear();
vec.push_back("hello");
vec.push_back("goodbye");
v2 = Value::MakeStringArray(std::move(vec));
ASSERT_NE(v1, v2);
REQUIRE(v1 != v2);
// empty
vec.clear();
v1 = Value::MakeStringArray(vec);
v2 = Value::MakeStringArray(std::move(vec));
ASSERT_EQ(v1, v2);
REQUIRE(v1 == v2);
}
} // namespace wpi::nt

View File

@@ -4,8 +4,10 @@
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <catch2/catch_session.hpp>
#include "gmock/gmock.h"
#include "wpi/nt/ntcore.h"
#include "wpi/util/timestamp.h"
@@ -17,18 +19,19 @@ int main(int argc, char** argv) {
std::fputc('\n', stderr);
}
});
::testing::InitGoogleMock(&argc, argv);
int ret = RUN_ALL_TESTS();
int ret = Catch::Session().run(argc, argv);
wpi::nt::ResetInstance(wpi::nt::GetDefaultInstance());
return ret;
}
extern "C" {
void __ubsan_on_report(void) {
FAIL() << "Encountered an undefined behavior sanitizer error";
std::puts("Encountered an undefined behavior sanitizer error");
std::_Exit(EXIT_FAILURE);
}
void __asan_on_error(void) {
FAIL() << "Encountered an address sanitizer error";
std::puts("Encountered an address sanitizer error");
std::_Exit(EXIT_FAILURE);
}
void __tsan_on_report(void) {
std::puts("Encountered a thread sanitizer error");

View File

@@ -4,48 +4,190 @@
#pragma once
#include <deque>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include "PubSubOptions.hpp"
#include "gmock/gmock.h"
#include "net/MessageHandler.hpp"
#include "wpi/nt/NetworkTableValue.hpp"
#include "wpi/util/json.hpp"
namespace wpi::nt::net {
class MockClientMessageHandler : public net::ClientMessageHandler {
public:
MOCK_METHOD(void, ClientPublish,
(int pubuid, std::string_view name, std::string_view typeStr,
const wpi::util::json& properties,
const PubSubOptionsImpl& options),
(override));
MOCK_METHOD(void, ClientUnpublish, (int pubuid), (override));
MOCK_METHOD(void, ClientSetProperties,
(std::string_view name, const wpi::util::json& update),
(override));
MOCK_METHOD(void, ClientSubscribe,
(int subuid, std::span<const std::string> prefixes,
const PubSubOptionsImpl& options),
(override));
MOCK_METHOD(void, ClientUnsubscribe, (int subuid), (override));
MOCK_METHOD(void, ClientSetValue, (int pubuid, const Value& value),
(override));
struct PublishCall {
int pubuid;
std::string name;
std::string typeStr;
wpi::util::json properties;
PubSubOptionsImpl options;
bool operator==(const PublishCall&) const = default;
};
struct UnpublishCall {
int pubuid;
bool operator==(const UnpublishCall&) const = default;
};
struct SetPropertiesCall {
std::string name;
wpi::util::json update;
bool operator==(const SetPropertiesCall&) const = default;
};
struct SubscribeCall {
int subuid;
std::vector<std::string> prefixes;
PubSubOptionsImpl options;
bool operator==(const SubscribeCall&) const = default;
};
struct UnsubscribeCall {
int subuid;
bool operator==(const UnsubscribeCall&) const = default;
};
struct SetValueCall {
int pubuid;
Value value;
bool operator==(const SetValueCall&) const = default;
};
using Call = std::variant<PublishCall, UnpublishCall, SetPropertiesCall,
SubscribeCall, UnsubscribeCall, SetValueCall>;
void ClientPublish(int pubuid, std::string_view name,
std::string_view typeStr,
const wpi::util::json& properties,
const PubSubOptionsImpl& options) override {
publishCalls.emplace_back(pubuid, std::string{name}, std::string{typeStr},
properties, options);
calls.emplace_back(publishCalls.back());
}
void ClientUnpublish(int pubuid) override {
unpublishCalls.emplace_back(pubuid);
calls.emplace_back(UnpublishCall{pubuid});
}
void ClientSetProperties(std::string_view name,
const wpi::util::json& update) override {
setPropertiesCalls.emplace_back(std::string{name}, update);
calls.emplace_back(setPropertiesCalls.back());
}
void ClientSubscribe(int subuid, std::span<const std::string> prefixes,
const PubSubOptionsImpl& options) override {
subscribeCalls.emplace_back(
subuid, std::vector<std::string>{prefixes.begin(), prefixes.end()},
options);
calls.emplace_back(subscribeCalls.back());
}
void ClientUnsubscribe(int subuid) override {
unsubscribeCalls.emplace_back(subuid);
calls.emplace_back(UnsubscribeCall{subuid});
}
void ClientSetValue(int pubuid, const Value& value) override {
setValueCalls.emplace_back(pubuid, value);
calls.emplace_back(setValueCalls.back());
}
std::vector<Call> calls;
std::vector<PublishCall> publishCalls;
std::vector<int> unpublishCalls;
std::vector<SetPropertiesCall> setPropertiesCalls;
std::vector<SubscribeCall> subscribeCalls;
std::vector<int> unsubscribeCalls;
std::vector<SetValueCall> setValueCalls;
};
class MockServerMessageHandler : public net::ServerMessageHandler {
public:
MOCK_METHOD(int, ServerAnnounce,
(std::string_view name, int id, std::string_view typeStr,
const wpi::util::json& properties, std::optional<int> pubuid),
(override));
MOCK_METHOD(void, ServerUnannounce, (std::string_view name, int id),
(override));
MOCK_METHOD(void, ServerPropertiesUpdate,
(std::string_view name, const wpi::util::json& update, bool ack),
(override));
MOCK_METHOD(void, ServerSetValue, (int topicuid, const Value& value),
(override));
struct AnnounceCall {
std::string name;
int id;
std::string typeStr;
wpi::util::json properties;
std::optional<int> pubuid;
bool operator==(const AnnounceCall&) const = default;
};
struct UnannounceCall {
std::string name;
int id;
bool operator==(const UnannounceCall&) const = default;
};
struct PropertiesUpdateCall {
std::string name;
wpi::util::json update;
bool ack;
bool operator==(const PropertiesUpdateCall&) const = default;
};
struct SetValueCall {
int topicuid;
Value value;
bool operator==(const SetValueCall&) const = default;
};
using Call = std::variant<AnnounceCall, UnannounceCall, PropertiesUpdateCall,
SetValueCall>;
int ServerAnnounce(std::string_view name, int id, std::string_view typeStr,
const wpi::util::json& properties,
std::optional<int> pubuid) override {
announceCalls.emplace_back(std::string{name}, id, std::string{typeStr},
properties, pubuid);
calls.emplace_back(announceCalls.back());
if (!announceReturns.empty()) {
int rv = announceReturns.front();
announceReturns.pop_front();
return rv;
}
return defaultAnnounceReturn;
}
void ServerUnannounce(std::string_view name, int id) override {
unannounceCalls.emplace_back(std::string{name}, id);
calls.emplace_back(unannounceCalls.back());
}
void ServerPropertiesUpdate(std::string_view name,
const wpi::util::json& update,
bool ack) override {
propertiesUpdateCalls.emplace_back(std::string{name}, update, ack);
calls.emplace_back(propertiesUpdateCalls.back());
}
void ServerSetValue(int topicuid, const Value& value) override {
setValueCalls.emplace_back(topicuid, value);
calls.emplace_back(setValueCalls.back());
}
int defaultAnnounceReturn = 0;
std::deque<int> announceReturns;
std::vector<Call> calls;
std::vector<AnnounceCall> announceCalls;
std::vector<UnannounceCall> unannounceCalls;
std::vector<PropertiesUpdateCall> propertiesUpdateCalls;
std::vector<SetValueCall> setValueCalls;
};
} // namespace wpi::nt::net

View File

@@ -4,27 +4,84 @@
#pragma once
#include "gmock/gmock.h"
#include <deque>
#include <optional>
#include <string>
#include <vector>
#include "net/NetworkInterface.hpp"
#include "wpi/nt/NetworkTableValue.hpp"
#include "wpi/util/json.hpp"
namespace wpi::nt::net {
class MockLocalStorage : public ILocalStorage {
public:
MOCK_METHOD(int, ServerAnnounce,
(std::string_view name, int id, std::string_view typeStr,
const wpi::util::json& properties, std::optional<int> pubuid),
(override));
MOCK_METHOD(void, ServerUnannounce, (std::string_view name, int id),
(override));
MOCK_METHOD(void, ServerPropertiesUpdate,
(std::string_view name, const wpi::util::json& update, bool ack),
(override));
MOCK_METHOD(void, ServerSetValue, (int topicId, const Value& value),
(override));
MOCK_METHOD(void, StartNetwork, (ClientMessageHandler * network), (override));
MOCK_METHOD(void, ClearNetwork, (), (override));
struct AnnounceCall {
std::string name;
int id;
std::string typeStr;
wpi::util::json properties;
std::optional<int> pubuid;
};
struct UnannounceCall {
std::string name;
int id;
};
struct PropertiesUpdateCall {
std::string name;
wpi::util::json update;
bool ack;
};
struct SetValueCall {
int topicId;
Value value;
};
int ServerAnnounce(std::string_view name, int id, std::string_view typeStr,
const wpi::util::json& properties,
std::optional<int> pubuid) override {
announceCalls.emplace_back(std::string{name}, id, std::string{typeStr},
properties, pubuid);
if (!announceReturns.empty()) {
int rv = announceReturns.front();
announceReturns.pop_front();
return rv;
}
return defaultAnnounceReturn;
}
void ServerUnannounce(std::string_view name, int id) override {
unannounceCalls.emplace_back(std::string{name}, id);
}
void ServerPropertiesUpdate(std::string_view name,
const wpi::util::json& update,
bool ack) override {
propertiesUpdateCalls.emplace_back(std::string{name}, update, ack);
}
void ServerSetValue(int topicId, const Value& value) override {
setValueCalls.emplace_back(topicId, value);
}
void StartNetwork(ClientMessageHandler* network) override {
startNetworkCalls.emplace_back(network);
}
void ClearNetwork() override { ++clearNetworkCalls; }
int defaultAnnounceReturn = 0;
std::deque<int> announceReturns;
std::vector<AnnounceCall> announceCalls;
std::vector<UnannounceCall> unannounceCalls;
std::vector<PropertiesUpdateCall> propertiesUpdateCalls;
std::vector<SetValueCall> setValueCalls;
std::vector<ClientMessageHandler*> startNetworkCalls;
int clearNetworkCalls = 0;
};
} // namespace wpi::nt::net

View File

@@ -6,12 +6,14 @@
#include <stdint.h>
#include <deque>
#include <functional>
#include <span>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
#include "gmock/gmock.h"
#include "net/WireConnection.hpp"
#include "wpi/util/raw_ostream.hpp"
@@ -19,11 +21,57 @@ namespace wpi::nt::net {
class MockWireConnection : public WireConnection {
public:
MOCK_METHOD(unsigned int, GetVersion, (), (const, override));
struct SendPingCall {
uint64_t time;
MOCK_METHOD(void, SendPing, (uint64_t time), (override));
bool operator==(const SendPingCall&) const = default;
};
MOCK_METHOD(bool, Ready, (), (const, override));
struct ReadyCall {
bool operator==(const ReadyCall&) const = default;
};
struct WriteTextCall {
std::string contents;
bool operator==(const WriteTextCall&) const = default;
};
struct WriteBinaryCall {
std::vector<uint8_t> contents;
bool operator==(const WriteBinaryCall&) const = default;
};
struct FlushCall {
bool operator==(const FlushCall&) const = default;
};
struct GetLastReceivedTimeCall {
bool operator==(const GetLastReceivedTimeCall&) const = default;
};
using Call =
std::variant<SendPingCall, ReadyCall, WriteTextCall, WriteBinaryCall,
FlushCall, GetLastReceivedTimeCall>;
unsigned int GetVersion() const override { return version; }
void SendPing(uint64_t time) override {
sendPingCalls.emplace_back(time);
calls.emplace_back(SendPingCall{time});
}
bool Ready() const override {
++readyCalls;
calls.emplace_back(ReadyCall{});
if (!readyReturns.empty()) {
bool rv = readyReturns.front();
readyReturns.pop_front();
return rv;
}
return defaultReady;
}
int WriteText(wpi::util::function_ref<void(wpi::util::raw_ostream& os)>
writer) override {
@@ -55,21 +103,101 @@ class MockWireConnection : public WireConnection {
DoSendBinary(binary);
}
MOCK_METHOD(int, DoWriteText, (std::string_view contents));
MOCK_METHOD(int, DoWriteBinary, (std::span<const uint8_t> contents));
int DoWriteText(std::string_view contents) {
writeTextCalls.emplace_back(contents);
calls.emplace_back(WriteTextCall{writeTextCalls.back()});
if (onWriteText) {
return onWriteText(contents);
}
if (!writeTextReturns.empty()) {
int rv = writeTextReturns.front();
writeTextReturns.pop_front();
return rv;
}
return defaultWriteTextReturn;
}
MOCK_METHOD(void, DoSendText, (std::string_view contents));
MOCK_METHOD(void, DoSendBinary, (std::span<const uint8_t> contents));
int DoWriteBinary(std::span<const uint8_t> contents) {
writeBinaryCalls.emplace_back(contents.begin(), contents.end());
calls.emplace_back(WriteBinaryCall{writeBinaryCalls.back()});
if (onWriteBinary) {
return onWriteBinary(contents);
}
if (!writeBinaryReturns.empty()) {
int rv = writeBinaryReturns.front();
writeBinaryReturns.pop_front();
return rv;
}
return defaultWriteBinaryReturn;
}
MOCK_METHOD(int, Flush, (), (override));
void DoSendText(std::string_view contents) {
sendTextCalls.emplace_back(contents);
}
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
MOCK_METHOD(uint64_t, GetLastReceivedTime, (), (const, override));
void DoSendBinary(std::span<const uint8_t> contents) {
sendBinaryCalls.emplace_back(contents.begin(), contents.end());
}
MOCK_METHOD(void, StopRead, (), (override));
MOCK_METHOD(void, StartRead, (), (override));
int Flush() override {
++flushCalls;
calls.emplace_back(FlushCall{});
if (!flushReturns.empty()) {
int rv = flushReturns.front();
flushReturns.pop_front();
return rv;
}
return defaultFlushReturn;
}
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
uint64_t GetLastFlushTime() const override { return lastFlushTime; }
uint64_t GetLastReceivedTime() const override {
++lastReceivedTimeCalls;
calls.emplace_back(GetLastReceivedTimeCall{});
if (!lastReceivedTimeReturns.empty()) {
uint64_t rv = lastReceivedTimeReturns.front();
lastReceivedTimeReturns.pop_front();
return rv;
}
return defaultLastReceivedTime;
}
void StopRead() override { ++stopReadCalls; }
void StartRead() override { ++startReadCalls; }
void Disconnect(std::string_view reason) override {
disconnectCalls.emplace_back(reason);
}
unsigned int version = 0x0401;
mutable std::deque<bool> readyReturns;
bool defaultReady = true;
std::deque<int> writeTextReturns;
int defaultWriteTextReturn = 0;
std::deque<int> writeBinaryReturns;
int defaultWriteBinaryReturn = 0;
std::deque<int> flushReturns;
int defaultFlushReturn = 0;
uint64_t lastFlushTime = 0;
mutable std::deque<uint64_t> lastReceivedTimeReturns;
uint64_t defaultLastReceivedTime = 0;
std::function<int(std::string_view)> onWriteText;
std::function<int(std::span<const uint8_t>)> onWriteBinary;
std::vector<uint64_t> sendPingCalls;
mutable int readyCalls = 0;
std::vector<std::string> writeTextCalls;
std::vector<std::vector<uint8_t>> writeBinaryCalls;
mutable std::vector<Call> calls;
std::vector<std::string> sendTextCalls;
std::vector<std::vector<uint8_t>> sendBinaryCalls;
int flushCalls = 0;
mutable int lastReceivedTimeCalls = 0;
int stopReadCalls = 0;
int startReadCalls = 0;
std::vector<std::string> disconnectCalls;
};
} // namespace wpi::nt::net

View File

@@ -13,7 +13,7 @@
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "net/Message.hpp"
#include "net/WireConnection.hpp"
@@ -110,9 +110,10 @@ std::pair<int, Value> DecodeBinary(std::span<const uint8_t> data,
int id = 0;
Value value;
std::string error;
EXPECT_TRUE(WireDecodeBinary(&data, &id, &value, &error, localTimeOffset))
<< error;
EXPECT_TRUE(data.empty());
bool decoded = WireDecodeBinary(&data, &id, &value, &error, localTimeOffset);
UNSCOPED_INFO(error);
CHECK(decoded);
CHECK(data.empty());
return {id, value};
}
@@ -124,67 +125,73 @@ ClientMessage Publish(int pubuid, std::string_view name) {
} // namespace
TEST(NetworkOutgoingQueueTest, UpdatePeriodCalcUsesGcdAndMinimum) {
EXPECT_EQ(UpdatePeriodCalc(UINT32_MAX, 100), 100u);
EXPECT_EQ(UpdatePeriodCalc(100, 40), 20u);
EXPECT_EQ(UpdatePeriodCalc(6, 4), kMinPeriodMs);
TEST_CASE("NetworkOutgoingQueueTest UpdatePeriodCalcUsesGcdAndMinimum",
"[ntcore][network-outgoing-queue]") {
CHECK(UpdatePeriodCalc(UINT32_MAX, 100) == 100u);
CHECK(UpdatePeriodCalc(100, 40) == 20u);
CHECK(UpdatePeriodCalc(6, 4) == kMinPeriodMs);
}
TEST(NetworkOutgoingQueueTest, CalculatePeriodUsesGcdAndMinimum) {
TEST_CASE("NetworkOutgoingQueueTest CalculatePeriodUsesGcdAndMinimum",
"[ntcore][network-outgoing-queue]") {
std::vector<uint32_t> periods{100, 40, 60};
EXPECT_EQ(CalculatePeriod(periods, [](uint32_t period) { return period; }),
20u);
CHECK(CalculatePeriod(periods, [](uint32_t period) { return period; }) ==
20u);
periods = {2, 4};
EXPECT_EQ(CalculatePeriod(periods, [](uint32_t period) { return period; }),
kMinPeriodMs);
CHECK(CalculatePeriod(periods, [](uint32_t period) { return period; }) ==
kMinPeriodMs);
}
TEST(NetworkOutgoingQueueTest, RemoteTextMessageWaitsForReadyAndMinimumPeriod) {
TEST_CASE(
"NetworkOutgoingQueueTest RemoteTextMessageWaitsForReadyAndMinimumPeriod",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ClientMessage> queue{wire, false};
queue.SendMessage(5, Publish(5, "test"));
queue.SendOutgoing(4, false);
EXPECT_TRUE(wire.textWrites.empty());
CHECK(wire.textWrites.empty());
wire.ready = false;
queue.SendOutgoing(5, false);
EXPECT_TRUE(wire.textWrites.empty());
CHECK(wire.textWrites.empty());
wire.ready = true;
queue.SendOutgoing(5, false);
ASSERT_EQ(wire.textWrites.size(), 1u);
EXPECT_EQ(wire.textWrites[0],
"{\"method\":\"publish\",\"params\":{\"name\":\"test\","
"\"properties\":{},\"pubuid\":5,\"type\":\"double\"}}");
EXPECT_EQ(wire.flushCount, 1);
REQUIRE(wire.textWrites.size() == 1u);
CHECK(wire.textWrites[0] ==
"{\"method\":\"publish\",\"params\":{\"name\":\"test\","
"\"properties\":{},\"pubuid\":5,\"type\":\"double\"}}");
CHECK(wire.flushCount == 1);
queue.SendOutgoing(10, false);
EXPECT_EQ(wire.textWrites.size(), 1u);
CHECK(wire.textWrites.size() == 1u);
}
TEST(NetworkOutgoingQueueTest, FlushWaitsForMinimumPeriod) {
TEST_CASE("NetworkOutgoingQueueTest FlushWaitsForMinimumPeriod",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ClientMessage> queue{wire, false};
queue.SendMessage(1, Publish(1, "first"));
queue.SendOutgoing(5, false);
ASSERT_EQ(wire.textWrites.size(), 1u);
REQUIRE(wire.textWrites.size() == 1u);
queue.SendMessage(2, Publish(2, "second"));
queue.SendOutgoing(6, true);
ASSERT_EQ(wire.textWrites.size(), 1u);
REQUIRE(wire.textWrites.size() == 1u);
queue.SendOutgoing(10, true);
ASSERT_EQ(wire.textWrites.size(), 2u);
EXPECT_EQ(wire.textWrites[1],
"{\"method\":\"publish\",\"params\":{\"name\":\"second\","
"\"properties\":{},\"pubuid\":2,\"type\":\"double\"}}");
REQUIRE(wire.textWrites.size() == 2u);
CHECK(wire.textWrites[1] ==
"{\"method\":\"publish\",\"params\":{\"name\":\"second\","
"\"properties\":{},\"pubuid\":2,\"type\":\"double\"}}");
}
TEST(NetworkOutgoingQueueTest, NormalValueKeepsOnlyNewestQueuedTimestamp) {
TEST_CASE("NetworkOutgoingQueueTest NormalValueKeepsOnlyNewestQueuedTimestamp",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -194,14 +201,15 @@ TEST(NetworkOutgoingQueueTest, NormalValueKeepsOnlyNewestQueuedTimestamp) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 1u);
REQUIRE(wire.binaryWrites.size() == 1u);
auto [id, value] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id, 7);
EXPECT_EQ(value.time(), 200);
EXPECT_EQ(value.GetDouble(), 2.0);
CHECK(id == 7);
CHECK(value.time() == 200);
CHECK(value.GetDouble() == 2.0);
}
TEST(NetworkOutgoingQueueTest, NormalValueShrinkUpdatesTotalSize) {
TEST_CASE("NetworkOutgoingQueueTest NormalValueShrinkUpdatesTotalSize",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -213,22 +221,23 @@ TEST(NetworkOutgoingQueueTest, NormalValueShrinkUpdatesTotalSize) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 3u);
REQUIRE(wire.binaryWrites.size() == 3u);
auto [id0, value0] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id0, 1);
EXPECT_EQ(value0.GetDouble(), 1.0);
CHECK(id0 == 1);
CHECK(value0.GetDouble() == 1.0);
auto [id1, value1] = DecodeBinary(wire.binaryWrites[1]);
EXPECT_EQ(id1, 1);
EXPECT_EQ(value1.GetDouble(), 2.0);
CHECK(id1 == 1);
CHECK(value1.GetDouble() == 2.0);
auto [id2, value2] = DecodeBinary(wire.binaryWrites[2]);
EXPECT_EQ(id2, 1);
EXPECT_EQ(value2.GetDouble(), 3.0);
CHECK(id2 == 1);
CHECK(value2.GetDouble() == 3.0);
}
TEST(NetworkOutgoingQueueTest, NormalValueReplacesAfterPeriodChange) {
TEST_CASE("NetworkOutgoingQueueTest NormalValueReplacesAfterPeriodChange",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -243,18 +252,20 @@ TEST(NetworkOutgoingQueueTest, NormalValueReplacesAfterPeriodChange) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 2u);
REQUIRE(wire.binaryWrites.size() == 2u);
auto [id0, value0] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id0, 2);
EXPECT_EQ(value0.GetDouble(), 2.0);
CHECK(id0 == 2);
CHECK(value0.GetDouble() == 2.0);
auto [id1, value1] = DecodeBinary(wire.binaryWrites[1]);
EXPECT_EQ(id1, 1);
EXPECT_EQ(value1.time(), 30);
EXPECT_EQ(value1.GetDouble(), 3.0);
CHECK(id1 == 1);
CHECK(value1.time() == 30);
CHECK(value1.GetDouble() == 3.0);
}
TEST(NetworkOutgoingQueueTest, NormalValueReplacesLastValueAfterPeriodChange) {
TEST_CASE(
"NetworkOutgoingQueueTest NormalValueReplacesLastValueAfterPeriodChange",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -270,23 +281,25 @@ TEST(NetworkOutgoingQueueTest, NormalValueReplacesLastValueAfterPeriodChange) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 3u);
REQUIRE(wire.binaryWrites.size() == 3u);
auto [id0, value0] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id0, 2);
EXPECT_EQ(value0.GetDouble(), 4.0);
CHECK(id0 == 2);
CHECK(value0.GetDouble() == 4.0);
auto [id1, value1] = DecodeBinary(wire.binaryWrites[1]);
EXPECT_EQ(id1, 1);
EXPECT_EQ(value1.time(), 10);
EXPECT_EQ(value1.GetDouble(), 1.0);
CHECK(id1 == 1);
CHECK(value1.time() == 10);
CHECK(value1.GetDouble() == 1.0);
auto [id2, value2] = DecodeBinary(wire.binaryWrites[2]);
EXPECT_EQ(id2, 1);
EXPECT_EQ(value2.time(), 30);
EXPECT_EQ(value2.GetDouble(), 3.0);
CHECK(id2 == 1);
CHECK(value2.time() == 30);
CHECK(value2.GetDouble() == 3.0);
}
TEST(NetworkOutgoingQueueTest, PeriodChangeMovesMixedMessagesInStableOrder) {
TEST_CASE(
"NetworkOutgoingQueueTest PeriodChangeMovesMixedMessagesInStableOrder",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ClientMessage> queue{wire, false};
@@ -303,25 +316,26 @@ TEST(NetworkOutgoingQueueTest, PeriodChangeMovesMixedMessagesInStableOrder) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.textWrites.size(), 3u);
EXPECT_EQ(wire.textWrites[0],
"{\"method\":\"publish\",\"params\":{\"name\":\"dest\","
"\"properties\":{},\"pubuid\":2,\"type\":\"double\"}}");
EXPECT_EQ(wire.textWrites[1],
"{\"method\":\"publish\",\"params\":{\"name\":\"first\","
"\"properties\":{},\"pubuid\":1,\"type\":\"double\"}}");
EXPECT_EQ(wire.textWrites[2],
"{\"method\":\"publish\",\"params\":{\"name\":\"second\","
"\"properties\":{},\"pubuid\":1,\"type\":\"double\"}}");
REQUIRE(wire.textWrites.size() == 3u);
CHECK(wire.textWrites[0] ==
"{\"method\":\"publish\",\"params\":{\"name\":\"dest\","
"\"properties\":{},\"pubuid\":2,\"type\":\"double\"}}");
CHECK(wire.textWrites[1] ==
"{\"method\":\"publish\",\"params\":{\"name\":\"first\","
"\"properties\":{},\"pubuid\":1,\"type\":\"double\"}}");
CHECK(wire.textWrites[2] ==
"{\"method\":\"publish\",\"params\":{\"name\":\"second\","
"\"properties\":{},\"pubuid\":1,\"type\":\"double\"}}");
ASSERT_EQ(wire.binaryWrites.size(), 1u);
REQUIRE(wire.binaryWrites.size() == 1u);
auto [id, value] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id, 1);
EXPECT_EQ(value.time(), 20);
EXPECT_EQ(value.GetDouble(), 2.0);
CHECK(id == 1);
CHECK(value.time() == 20);
CHECK(value.GetDouble() == 2.0);
}
TEST(NetworkOutgoingQueueTest, SendAllValuesAppendBelowBackpressureLimit) {
TEST_CASE("NetworkOutgoingQueueTest SendAllValuesAppendBelowBackpressureLimit",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -330,17 +344,19 @@ TEST(NetworkOutgoingQueueTest, SendAllValuesAppendBelowBackpressureLimit) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 2u);
REQUIRE(wire.binaryWrites.size() == 2u);
auto [id0, value0] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id0, 1);
EXPECT_EQ(value0.GetDouble(), 1.0);
CHECK(id0 == 1);
CHECK(value0.GetDouble() == 1.0);
auto [id1, value1] = DecodeBinary(wire.binaryWrites[1]);
EXPECT_EQ(id1, 1);
EXPECT_EQ(value1.GetDouble(), 2.0);
CHECK(id1 == 1);
CHECK(value1.GetDouble() == 2.0);
}
TEST(NetworkOutgoingQueueTest, SendAllValuesCoalesceAfterBackpressureLimit) {
TEST_CASE(
"NetworkOutgoingQueueTest SendAllValuesCoalesceAfterBackpressureLimit",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -352,13 +368,15 @@ TEST(NetworkOutgoingQueueTest, SendAllValuesCoalesceAfterBackpressureLimit) {
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 1u);
REQUIRE(wire.binaryWrites.size() == 1u);
auto [id, value] = DecodeBinary(wire.binaryWrites[0]);
EXPECT_EQ(id, 1);
EXPECT_EQ(value.GetDouble(), 3.0);
CHECK(id == 1);
CHECK(value.GetDouble() == 3.0);
}
TEST(NetworkOutgoingQueueTest, PartialWriteRetainsUnsentValueForReplacement) {
TEST_CASE(
"NetworkOutgoingQueueTest PartialWriteRetainsUnsentValueForReplacement",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
wire.binaryWriteReturns = {0, 1};
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -367,19 +385,20 @@ TEST(NetworkOutgoingQueueTest, PartialWriteRetainsUnsentValueForReplacement) {
queue.SendValue(2, Value::MakeDouble(2.0, 20), ValueSendMode::kNormal);
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 2u);
REQUIRE(wire.binaryWrites.size() == 2u);
queue.SendValue(2, Value::MakeDouble(3.0, 30), ValueSendMode::kNormal);
queue.SendOutgoing(10, true);
ASSERT_EQ(wire.binaryWrites.size(), 3u);
REQUIRE(wire.binaryWrites.size() == 3u);
auto [id, value] = DecodeBinary(wire.binaryWrites[2]);
EXPECT_EQ(id, 2);
EXPECT_EQ(value.time(), 30);
EXPECT_EQ(value.GetDouble(), 3.0);
CHECK(id == 2);
CHECK(value.time() == 30);
CHECK(value.GetDouble() == 3.0);
}
TEST(NetworkOutgoingQueueTest, FlushBackpressureRetainsLastUnsentValue) {
TEST_CASE("NetworkOutgoingQueueTest FlushBackpressureRetainsLastUnsentValue",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
wire.flushReturns = {1};
NetworkOutgoingQueue<ServerMessage> queue{wire, false};
@@ -388,32 +407,34 @@ TEST(NetworkOutgoingQueueTest, FlushBackpressureRetainsLastUnsentValue) {
queue.SendValue(2, Value::MakeDouble(2.0, 20), ValueSendMode::kNormal);
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.binaryWrites.size(), 2u);
REQUIRE(wire.binaryWrites.size() == 2u);
queue.SendOutgoing(10, true);
ASSERT_EQ(wire.binaryWrites.size(), 3u);
REQUIRE(wire.binaryWrites.size() == 3u);
auto [id, value] = DecodeBinary(wire.binaryWrites[2]);
EXPECT_EQ(id, 2);
EXPECT_EQ(value.GetDouble(), 2.0);
CHECK(id == 2);
CHECK(value.GetDouble() == 2.0);
}
TEST(NetworkOutgoingQueueTest, ClientImmediateValueAppliesTimeOffset) {
TEST_CASE("NetworkOutgoingQueueTest ClientImmediateValueAppliesTimeOffset",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ClientMessage> queue{wire, false};
queue.SetTimeOffset(5);
queue.SendValue(3, Value::MakeDouble(1.0, 10), ValueSendMode::kImm);
ASSERT_EQ(wire.binarySends.size(), 1u);
REQUIRE(wire.binarySends.size() == 1u);
auto [id, value] = DecodeBinary(wire.binarySends[0], -5);
EXPECT_EQ(id, 3);
EXPECT_EQ(value.time(), 10);
EXPECT_EQ(value.server_time(), 15);
EXPECT_EQ(value.GetDouble(), 1.0);
CHECK(id == 3);
CHECK(value.time() == 10);
CHECK(value.server_time() == 15);
CHECK(value.GetDouble() == 1.0);
}
TEST(NetworkOutgoingQueueTest, LocalQueueSendsImmediately) {
TEST_CASE("NetworkOutgoingQueueTest LocalQueueSendsImmediately",
"[ntcore][network-outgoing-queue]") {
RecordingWireConnection wire;
NetworkOutgoingQueue<ClientMessage> queue{wire, true};
@@ -421,13 +442,13 @@ TEST(NetworkOutgoingQueueTest, LocalQueueSendsImmediately) {
queue.SendValue(5, Value::MakeDouble(1.0, 10), ValueSendMode::kNormal);
queue.SendOutgoing(5, true);
ASSERT_EQ(wire.textSends.size(), 1u);
EXPECT_EQ(wire.textSends[0],
"{\"method\":\"publish\",\"params\":{\"name\":\"local\","
"\"properties\":{},\"pubuid\":5,\"type\":\"double\"}}");
ASSERT_EQ(wire.binarySends.size(), 1u);
EXPECT_TRUE(wire.textWrites.empty());
EXPECT_TRUE(wire.binaryWrites.empty());
REQUIRE(wire.textSends.size() == 1u);
CHECK(wire.textSends[0] ==
"{\"method\":\"publish\",\"params\":{\"name\":\"local\","
"\"properties\":{},\"pubuid\":5,\"type\":\"double\"}}");
REQUIRE(wire.binarySends.size() == 1u);
CHECK(wire.textWrites.empty());
CHECK(wire.binaryWrites.empty());
}
} // namespace wpi::nt::net

View File

@@ -6,180 +6,237 @@
#include <string>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "../MockAssertions.hpp"
#include "../MockLogger.hpp"
#include "../PubSubOptionsMatcher.hpp"
#include "../TestPrinters.hpp"
#include "MockMessageHandler.hpp"
#include "PubSubOptions.hpp"
#include "gmock/gmock.h"
#include "net/MessageHandler.hpp"
#include "wpi/nt/NetworkTableValue.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/raw_ostream.hpp"
using namespace std::string_view_literals;
using testing::_;
using testing::MockFunction;
using testing::StrictMock;
namespace wpi::nt {
class WireDecodeTextClientTest : public ::testing::Test {
class WireDecodeTextClientTest {
public:
StrictMock<net::MockClientMessageHandler> handler;
StrictMock<wpi::MockLogger> logger;
net::MockClientMessageHandler handler;
wpi::MockLogger logger;
};
class WireDecodeTextServerTest : public ::testing::Test {
class WireDecodeTextServerTest {
public:
StrictMock<net::MockServerMessageHandler> handler;
StrictMock<wpi::MockLogger> logger;
net::MockServerMessageHandler handler;
wpi::MockLogger logger;
};
TEST_F(WireDecodeTextClientTest, EmptyArray) {
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest EmptyArray",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[]", handler, logger);
logger.CheckMessages({});
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorEmpty) {
EXPECT_CALL(logger,
Call(_, _, _, "could not decode JSON message: absent_value"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorEmpty",
"[ntcore][wire][decoder]") {
net::WireDecodeText("", handler, logger);
logger.CheckMessage(NT_LOG_WARNING,
"could not decode JSON message: absent_value"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorBadJson1) {
EXPECT_CALL(logger,
Call(_, _, _, "could not decode JSON message: unexpected_eof"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorBadJson1",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[", handler, logger);
logger.CheckMessage(NT_LOG_WARNING,
"could not decode JSON message: unexpected_eof"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorBadJson2) {
EXPECT_CALL(logger,
Call(_, _, _, "could not decode JSON message: unexpected_eof"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorBadJson2",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{", handler, logger);
logger.CheckMessage(NT_LOG_WARNING,
"could not decode JSON message: unexpected_eof"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorNotArray) {
EXPECT_CALL(logger, Call(_, _, _, "expected JSON array at top level"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorNotArray",
"[ntcore][wire][decoder]") {
net::WireDecodeText("{}", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "expected JSON array at top level"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorMessageNotObject) {
EXPECT_CALL(logger, Call(_, _, _, "0: expected message to be an object"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorMessageNotObject",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[5]", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: expected message to be an object"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorNoMethodKey) {
EXPECT_CALL(logger, Call(_, _, _, "0: no method key"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorNoMethodKey",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{}]", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: no method key"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorMethodNotString) {
EXPECT_CALL(logger, Call(_, _, _, "0: method must be a string"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorMethodNotString",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{\"method\":5}]", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: method must be a string"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorNoParamsKey) {
EXPECT_CALL(logger, Call(_, _, _, "0: no params key"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorNoParamsKey",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{\"method\":\"a\"}]", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: no params key"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorParamsNotObject) {
EXPECT_CALL(logger, Call(_, _, _, "0: params must be an object"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorParamsNotObject",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{\"method\":\"a\",\"params\":5}]", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: params must be an object"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, ErrorUnknownMethod) {
EXPECT_CALL(logger, Call(_, _, _, "0: unrecognized method 'a'"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest ErrorUnknownMethod",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{\"method\":\"a\",\"params\":{}}]", handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: unrecognized method 'a'"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, PublishPropsEmpty) {
EXPECT_CALL(
handler,
ClientPublish(5, std::string_view{"test"}, std::string_view{"double"},
wpi::util::json::object(), PubSubOptionsEq({})));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest PublishPropsEmpty",
"[ntcore][wire][decoder]") {
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{},\"pubuid\":5,\"type\":\"double\"}}]",
handler, logger);
logger.CheckMessages({});
CheckClientMessageCounts(handler, {.publish = 1});
CheckPublish(handler.publishCalls.back(), 5, "test", "double",
wpi::util::json::object());
EXPECT_CALL(
handler,
ClientPublish(5, std::string_view{"test"}, std::string_view{"double"},
wpi::util::json::object(), PubSubOptionsEq({})));
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"pubuid\":5,\"type\":\"double\"}}]",
handler, logger);
logger.CheckMessages({});
CheckClientMessageCounts(handler, {.publish = 2});
CheckPublish(handler.publishCalls.back(), 5, "test", "double",
wpi::util::json::object());
}
TEST_F(WireDecodeTextClientTest, PublishProps) {
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest PublishProps",
"[ntcore][wire][decoder]") {
auto props = wpi::util::json::object("k", 6);
EXPECT_CALL(handler, ClientPublish(5, std::string_view{"test"},
std::string_view{"double"}, props,
PubSubOptionsEq({})));
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{\"k\":6},"
"\"pubuid\":5,\"type\":\"double\"}}]",
handler, logger);
logger.CheckMessages({});
CheckClientMessageCounts(handler, {.publish = 1});
CheckPublish(handler.publishCalls[0], 5, "test", "double", props);
}
TEST_F(WireDecodeTextClientTest, PublishPropsError) {
EXPECT_CALL(logger, Call(_, _, _, "0: properties must be an object"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest PublishPropsError",
"[ntcore][wire][decoder]") {
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":[\"k\"],"
"\"pubuid\":5,\"type\":\"double\"}}]",
handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: properties must be an object"sv);
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, PublishError) {
EXPECT_CALL(logger, Call(_, _, _, "0: no name key"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest PublishError",
"[ntcore][wire][decoder]") {
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"pubuid\":5,\"type\":\"double\"}}]",
handler, logger);
logger.CheckMessage(NT_LOG_WARNING, "0: no name key"sv);
CheckNoClientCalls(handler);
EXPECT_CALL(logger, Call(_, _, _, "0: no type key"sv));
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"pubuid\":5}}]",
handler, logger);
logger.CheckMessages({{NT_LOG_WARNING, "0: no name key"sv},
{NT_LOG_WARNING, "0: no type key"sv}});
CheckNoClientCalls(handler);
EXPECT_CALL(logger, Call(_, _, _, "0: no pubuid key"sv));
net::WireDecodeText(
"[{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"type\":\"double\"}}]",
handler, logger);
logger.CheckMessages({{NT_LOG_WARNING, "0: no name key"sv},
{NT_LOG_WARNING, "0: no type key"sv},
{NT_LOG_WARNING, "0: no pubuid key"sv}});
CheckNoClientCalls(handler);
}
TEST_F(WireDecodeTextClientTest, Unpublish) {
EXPECT_CALL(handler, ClientUnpublish(5));
TEST_CASE_METHOD(WireDecodeTextClientTest, "WireDecodeTextClientTest Unpublish",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{\"method\":\"unpublish\",\"params\":{\"pubuid\":5}}]",
handler, logger);
logger.CheckMessages({});
CheckClientMessageCounts(handler, {.unpublish = 1});
CHECK(handler.unpublishCalls[0] == 5);
}
TEST_F(WireDecodeTextClientTest, UnpublishMultiple) {
EXPECT_CALL(handler, ClientUnpublish(5));
EXPECT_CALL(handler, ClientUnpublish(6));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest UnpublishMultiple",
"[ntcore][wire][decoder]") {
net::WireDecodeText(
"[{\"method\":\"unpublish\",\"params\":{\"pubuid\":5}},{\"method\":"
"\"unpublish\",\"params\":{\"pubuid\":6}}]",
handler, logger);
logger.CheckMessages({});
CheckClientMessageCounts(handler, {.unpublish = 2});
CHECK(handler.unpublishCalls[0] == 5);
CHECK(handler.unpublishCalls[1] == 6);
}
TEST_F(WireDecodeTextClientTest, UnpublishError) {
EXPECT_CALL(logger, Call(_, _, _, "0: no pubuid key"sv));
TEST_CASE_METHOD(WireDecodeTextClientTest,
"WireDecodeTextClientTest UnpublishError",
"[ntcore][wire][decoder]") {
net::WireDecodeText("[{\"method\":\"unpublish\",\"params\":{}}]", handler,
logger);
logger.CheckMessage(NT_LOG_WARNING, "0: no pubuid key"sv);
CheckNoClientCalls(handler);
EXPECT_CALL(logger, Call(_, _, _, "0: pubuid must be a number"sv));
net::WireDecodeText(
"[{\"method\":\"unpublish\",\"params\":{\"pubuid\":\"5\"}}]", handler,
logger);
logger.CheckMessages({{NT_LOG_WARNING, "0: no pubuid key"sv},
{NT_LOG_WARNING, "0: pubuid must be a number"sv}});
CheckNoClientCalls(handler);
}
} // namespace wpi::nt

View File

@@ -4,28 +4,43 @@
#include "net/WireEncoder.hpp"
#include <stdint.h>
#include <algorithm>
#include <cstddef>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "../TestPrinters.hpp"
#include "Handle.hpp"
#include "PubSubOptions.hpp"
#include "gmock/gmock-matchers.h"
#include "net/Message.hpp"
#include "wpi/nt/NetworkTableValue.hpp"
#include "wpi/util/SpanMatcher.hpp"
#include "wpi/util/json.hpp"
#include "wpi/util/raw_ostream.hpp"
using namespace std::string_view_literals;
namespace wpi::nt {
namespace {
class WireEncoderTextTest : public ::testing::Test {
std::span<const uint8_t> operator""_us(const char* str, size_t len) {
return {reinterpret_cast<const uint8_t*>(str), len};
}
bool SpanEquals(std::span<const uint8_t> actual,
std::span<const uint8_t> expected) {
return actual.size() == expected.size() &&
std::equal(actual.begin(), actual.end(), expected.begin());
}
} // namespace
class WireEncoderTextTest {
protected:
std::string out;
wpi::util::raw_string_ostream os{out};
@@ -34,266 +49,304 @@ class WireEncoderTextTest : public ::testing::Test {
}
};
class WireEncoderBinaryTest : public ::testing::Test {
class WireEncoderBinaryTest {
protected:
std::vector<uint8_t> out;
wpi::util::raw_uvector_ostream os{out};
};
TEST_F(WireEncoderTextTest, PublishPropsEmpty) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest PublishPropsEmpty",
"[ntcore][wire][encoder]") {
net::WireEncodePublish(os, 5, "test", "double", wpi::util::json::object());
ASSERT_EQ(
os.str(),
"{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{},\"pubuid\":5,\"type\":\"double\"}}");
REQUIRE(os.str() ==
"{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{},\"pubuid\":5,"
"\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, PublishProps) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest PublishProps",
"[ntcore][wire][encoder]") {
net::WireEncodePublish(os, 5, "test", "double",
wpi::util::json::object("k", 6));
ASSERT_EQ(os.str(),
"{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{\"k\":6},"
"\"pubuid\":5,\"type\":\"double\"}}");
REQUIRE(os.str() ==
"{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{\"k\":6},"
"\"pubuid\":5,\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, Unpublish) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest Unpublish",
"[ntcore][wire][encoder]") {
net::WireEncodeUnpublish(os, 5);
ASSERT_EQ(os.str(), "{\"method\":\"unpublish\",\"params\":{\"pubuid\":5}}");
REQUIRE(os.str() == "{\"method\":\"unpublish\",\"params\":{\"pubuid\":5}}");
}
TEST_F(WireEncoderTextTest, SetProperties) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest SetProperties",
"[ntcore][wire][encoder]") {
net::WireEncodeSetProperties(os, "test", wpi::util::json::object("k", 6));
ASSERT_EQ(os.str(),
"{\"method\":\"setproperties\",\"params\":{"
"\"name\":\"test\",\"update\":{\"k\":6}}}");
REQUIRE(os.str() ==
"{\"method\":\"setproperties\",\"params\":{"
"\"name\":\"test\",\"update\":{\"k\":6}}}");
}
TEST_F(WireEncoderTextTest, Subscribe) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest Subscribe",
"[ntcore][wire][encoder]") {
net::WireEncodeSubscribe(os, 5, std::span<const std::string_view>{{"a", "b"}},
PubSubOptions{});
ASSERT_EQ(os.str(),
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
REQUIRE(os.str() ==
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, SubscribeSendAll) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest SubscribeSendAll",
"[ntcore][wire][encoder]") {
PubSubOptionsImpl options;
options.sendAll = true;
net::WireEncodeSubscribe(os, 5, std::span<const std::string_view>{{"a", "b"}},
options);
ASSERT_EQ(os.str(),
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{\"all\":true},\"topics\":[\"a\",\"b\"],"
"\"subuid\":5}}");
REQUIRE(os.str() ==
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{\"all\":true},\"topics\":[\"a\",\"b\"],"
"\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, SubscribePeriodic) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest SubscribePeriodic",
"[ntcore][wire][encoder]") {
PubSubOptionsImpl options;
options.periodicMs = 500u;
net::WireEncodeSubscribe(os, 5, std::span<const std::string_view>{{"a", "b"}},
options);
ASSERT_EQ(os.str(),
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{\"periodic\":0.5},\"topics\":[\"a\",\"b\"],"
"\"subuid\":5}}");
REQUIRE(os.str() ==
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{\"periodic\":0.5},\"topics\":[\"a\",\"b\"],"
"\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, SubscribeAllOptions) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest SubscribeAllOptions",
"[ntcore][wire][encoder]") {
PubSubOptionsImpl options;
options.sendAll = true;
options.periodicMs = 500u;
net::WireEncodeSubscribe(os, 5, std::span<const std::string_view>{{"a", "b"}},
options);
ASSERT_EQ(os.str(),
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{\"all\":true,\"periodic\":0.5},"
"\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
REQUIRE(os.str() ==
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{\"all\":true,\"periodic\":0.5},"
"\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, Unsubscribe) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest Unsubscribe",
"[ntcore][wire][encoder]") {
net::WireEncodeUnsubscribe(os, 5);
ASSERT_EQ(os.str(), "{\"method\":\"unsubscribe\",\"params\":{\"subuid\":5}}");
REQUIRE(os.str() == "{\"method\":\"unsubscribe\",\"params\":{\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, Announce) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest Announce",
"[ntcore][wire][encoder]") {
net::WireEncodeAnnounce(os, "test", 5, "double", wpi::util::json::object(),
std::nullopt);
ASSERT_EQ(os.str(),
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"type\":\"double\"}}");
REQUIRE(os.str() ==
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, AnnounceProperties) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest AnnounceProperties",
"[ntcore][wire][encoder]") {
net::WireEncodeAnnounce(os, "test", 5, "double",
wpi::util::json::object("k", 6), std::nullopt);
ASSERT_EQ(os.str(),
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{\"k\":6},\"type\":\"double\"}}");
REQUIRE(os.str() ==
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{\"k\":6},\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, AnnouncePubuid) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest AnnouncePubuid",
"[ntcore][wire][encoder]") {
net::WireEncodeAnnounce(os, "test", 5, "double", wpi::util::json::object(),
6);
ASSERT_EQ(os.str(),
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"pubuid\":6,\"type\":\"double\"}}");
REQUIRE(os.str() ==
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"pubuid\":6,\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, Unannounce) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest Unannounce",
"[ntcore][wire][encoder]") {
net::WireEncodeUnannounce(os, "test", 5);
ASSERT_EQ(
os.str(),
REQUIRE(
os.str() ==
"{\"method\":\"unannounce\",\"params\":{\"id\":5,\"name\":\"test\"}}");
}
TEST_F(WireEncoderTextTest, MessagePublish) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest MessagePublish",
"[ntcore][wire][encoder]") {
net::ClientMessage msg{net::PublishMsg{
5, "test", "double", wpi::util::json::object("k", 6), {}}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{\"k\":6},"
"\"pubuid\":5,\"type\":\"double\"}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() ==
"{\"method\":\"publish\",\"params\":{"
"\"name\":\"test\",\"properties\":{\"k\":6},"
"\"pubuid\":5,\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, MessageUnpublish) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest MessageUnpublish",
"[ntcore][wire][encoder]") {
net::ClientMessage msg{net::UnpublishMsg{5}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(), "{\"method\":\"unpublish\",\"params\":{\"pubuid\":5}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() == "{\"method\":\"unpublish\",\"params\":{\"pubuid\":5}}");
}
TEST_F(WireEncoderTextTest, MessageSetProperties) {
TEST_CASE_METHOD(WireEncoderTextTest,
"WireEncoderTextTest MessageSetProperties",
"[ntcore][wire][encoder]") {
net::ClientMessage msg{
net::SetPropertiesMsg{"test", wpi::util::json::object("k", 6)}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"setproperties\",\"params\":{"
"\"name\":\"test\",\"update\":{\"k\":6}}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() ==
"{\"method\":\"setproperties\",\"params\":{"
"\"name\":\"test\",\"update\":{\"k\":6}}}");
}
TEST_F(WireEncoderTextTest, MessageSubscribe) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest MessageSubscribe",
"[ntcore][wire][encoder]") {
net::ClientMessage msg{net::SubscribeMsg{5, {"a", "b"}, {}}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() ==
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, MessageUnsubscribe) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest MessageUnsubscribe",
"[ntcore][wire][encoder]") {
net::ClientMessage msg{net::UnsubscribeMsg{5}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(), "{\"method\":\"unsubscribe\",\"params\":{\"subuid\":5}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() == "{\"method\":\"unsubscribe\",\"params\":{\"subuid\":5}}");
}
TEST_F(WireEncoderTextTest, MessageAnnounce) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest MessageAnnounce",
"[ntcore][wire][encoder]") {
net::ServerMessage msg{net::AnnounceMsg{"test", 5, "double", std::nullopt,
wpi::util::json::object()}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"type\":\"double\"}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() ==
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, MessageAnnounceProperties) {
TEST_CASE_METHOD(WireEncoderTextTest,
"WireEncoderTextTest MessageAnnounceProperties",
"[ntcore][wire][encoder]") {
net::ServerMessage msg{net::AnnounceMsg{"test", 5, "double", std::nullopt,
wpi::util::json::object("k", 6)}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{\"k\":6},\"type\":\"double\"}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() ==
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{\"k\":6},\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, MessageAnnouncePubuid) {
TEST_CASE_METHOD(WireEncoderTextTest,
"WireEncoderTextTest MessageAnnouncePubuid",
"[ntcore][wire][encoder]") {
net::ServerMessage msg{
net::AnnounceMsg{"test", 5, "double", 6, wpi::util::json::object()}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"pubuid\":6,\"type\":\"double\"}}");
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(os.str() ==
"{\"method\":\"announce\",\"params\":{\"id\":5,\"name\":\"test\","
"\"properties\":{},\"pubuid\":6,\"type\":\"double\"}}");
}
TEST_F(WireEncoderTextTest, MessageUnannounce) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest MessageUnannounce",
"[ntcore][wire][encoder]") {
net::ServerMessage msg{net::UnannounceMsg{"test", 5}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(
os.str(),
REQUIRE(net::WireEncodeText(os, msg));
REQUIRE(
os.str() ==
"{\"method\":\"unannounce\",\"params\":{\"id\":5,\"name\":\"test\"}}");
}
TEST_F(WireEncoderTextTest, ServerMessageEmpty) {
ASSERT_FALSE(net::WireEncodeText(os, net::ServerMessage{}));
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest ServerMessageEmpty",
"[ntcore][wire][encoder]") {
REQUIRE_FALSE(net::WireEncodeText(os, net::ServerMessage{}));
}
TEST_F(WireEncoderTextTest, ServerMessageValue) {
TEST_CASE_METHOD(WireEncoderTextTest, "WireEncoderTextTest ServerMessageValue",
"[ntcore][wire][encoder]") {
net::ServerMessage msg{net::ServerValueMsg{}};
ASSERT_FALSE(net::WireEncodeText(os, msg));
REQUIRE_FALSE(net::WireEncodeText(os, msg));
}
TEST_F(WireEncoderBinaryTest, Boolean) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest Boolean",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeBoolean(true));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x00\xc3"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x00\xc3"_us));
}
TEST_F(WireEncoderBinaryTest, Integer) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest Integer",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeInteger(7));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x02\x07"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x02\x07"_us));
}
TEST_F(WireEncoderBinaryTest, Float) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest Float",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeFloat(2.5));
ASSERT_THAT(out,
wpi::util::SpanEq("\x94\x05\x06\x03\xca\x40\x20\x00\x00"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x03\xca\x40\x20\x00\x00"_us));
}
TEST_F(WireEncoderBinaryTest, Double) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest Double",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeDouble(2.5));
ASSERT_THAT(out,
wpi::util::SpanEq(
"\x94\x05\x06\x01\xcb\x40\x04\x00\x00\x00\x00\x00\x00"_us));
REQUIRE(SpanEquals(
out, "\x94\x05\x06\x01\xcb\x40\x04\x00\x00\x00\x00\x00\x00"_us));
}
TEST_F(WireEncoderBinaryTest, String) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest String",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeString("hello"));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x04\xa5hello"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x04\xa5hello"_us));
}
TEST_F(WireEncoderBinaryTest, Raw) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest Raw",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeRaw("hello"_us));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x05\xc4\x05hello"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x05\xc4\x05hello"_us));
}
TEST_F(WireEncoderBinaryTest, BooleanArray) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest BooleanArray",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeBooleanArray({true, false, true}));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x10\x93\xc3\xc2\xc3"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x10\x93\xc3\xc2\xc3"_us));
}
TEST_F(WireEncoderBinaryTest, IntegerArray) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest IntegerArray",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeIntegerArray({1, 2, 4}));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x12\x93\x01\x02\x04"_us));
REQUIRE(SpanEquals(out, "\x94\x05\x06\x12\x93\x01\x02\x04"_us));
}
TEST_F(WireEncoderBinaryTest, FloatArray) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest FloatArray",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeFloatArray({1, 2, 3}));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x13\x93"
"\xca\x3f\x80\x00\x00"
"\xca\x40\x00\x00\x00"
"\xca\x40\x40\x00\x00"_us));
REQUIRE(SpanEquals(out,
"\x94\x05\x06\x13\x93"
"\xca\x3f\x80\x00\x00"
"\xca\x40\x00\x00\x00"
"\xca\x40\x40\x00\x00"_us));
}
TEST_F(WireEncoderBinaryTest, DoubleArray) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest DoubleArray",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeDoubleArray({1, 2, 3}));
ASSERT_THAT(out,
wpi::util::SpanEq("\x94\x05\x06\x11\x93"
"\xcb\x3f\xf0\x00\x00\x00\x00\x00\x00"
"\xcb\x40\x00\x00\x00\x00\x00\x00\x00"
"\xcb\x40\x08\x00\x00\x00\x00\x00\x00"_us));
REQUIRE(SpanEquals(out,
"\x94\x05\x06\x11\x93"
"\xcb\x3f\xf0\x00\x00\x00\x00\x00\x00"
"\xcb\x40\x00\x00\x00\x00\x00\x00\x00"
"\xcb\x40\x08\x00\x00\x00\x00\x00\x00"_us));
}
TEST_F(WireEncoderBinaryTest, StringArray) {
TEST_CASE_METHOD(WireEncoderBinaryTest, "WireEncoderBinaryTest StringArray",
"[ntcore][wire][encoder]") {
net::WireEncodeBinary(os, 5, 6, Value::MakeStringArray({"hello", "bye"}));
ASSERT_THAT(out, wpi::util::SpanEq("\x94\x05\x06\x14\x92\xa5hello\xa3"
"bye"_us));
REQUIRE(SpanEquals(out,
"\x94\x05\x06\x14\x92\xa5hello\xa3"
"bye"_us));
}
} // namespace wpi::nt

View File

@@ -4,84 +4,125 @@
#include "server/ServerImpl.hpp"
#include <stddef.h>
#include <stdint.h>
#include <concepts>
#include <functional>
#include <span>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
#include <gtest/gtest.h>
#include <catch2/catch_test_macros.hpp>
#include "../MockAssertions.hpp"
#include "../MockLogger.hpp"
#include "../PubSubOptionsMatcher.hpp"
#include "../TestPrinters.hpp"
#include "../ValueMatcher.hpp"
#include "../net/MockClientMessageQueue.hpp"
#include "../net/MockMessageHandler.hpp"
#include "../net/MockWireConnection.hpp"
#include "Handle.hpp"
#include "gmock/gmock.h"
#include "net/Message.hpp"
#include "net/WireEncoder.hpp"
#include "wpi/nt/ntcore_c.h"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/SpanMatcher.hpp"
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Property;
using ::testing::Return;
using ::testing::StrEq;
using MockSetPeriodicFunc = ::testing::MockFunction<void(uint32_t repeatMs)>;
using MockConnected3Func =
::testing::MockFunction<void(std::string_view name, uint16_t proto)>;
namespace wpi::nt {
namespace {
class ServerImplTest : public ::testing::Test {
class SetPeriodicRecorder {
public:
::testing::StrictMock<net::MockServerMessageHandler> local;
::testing::StrictMock<net::MockClientMessageQueue> queue;
std::function<void(uint32_t)> AsStdFunction() {
return [this](uint32_t repeatMs) { calls.emplace_back(repeatMs); };
}
std::vector<uint32_t> calls;
};
struct WireConnectionCounts {
size_t sendPing = 0;
size_t ready = 0;
size_t writeText = 0;
size_t writeBinary = 0;
size_t flush = 0;
size_t lastReceivedTime = 0;
size_t sendText = 0;
size_t sendBinary = 0;
size_t stopRead = 0;
size_t startRead = 0;
size_t disconnect = 0;
};
void CheckWireConnectionCounts(const net::MockWireConnection& wire,
const WireConnectionCounts& expected = {}) {
REQUIRE(wire.sendPingCalls.size() == expected.sendPing);
REQUIRE(static_cast<size_t>(wire.readyCalls) == expected.ready);
REQUIRE(wire.writeTextCalls.size() == expected.writeText);
REQUIRE(wire.writeBinaryCalls.size() == expected.writeBinary);
REQUIRE(static_cast<size_t>(wire.flushCalls) == expected.flush);
REQUIRE(static_cast<size_t>(wire.lastReceivedTimeCalls) ==
expected.lastReceivedTime);
REQUIRE(wire.sendTextCalls.size() == expected.sendText);
REQUIRE(wire.sendBinaryCalls.size() == expected.sendBinary);
REQUIRE(static_cast<size_t>(wire.stopReadCalls) == expected.stopRead);
REQUIRE(static_cast<size_t>(wire.startReadCalls) == expected.startRead);
REQUIRE(wire.disconnectCalls.size() == expected.disconnect);
}
template <typename... Expected, typename... Calls>
void CheckCallOrder(const std::vector<std::variant<Calls...>>& calls) {
REQUIRE(calls.size() == sizeof...(Expected));
size_t index = 0;
(
[&] {
CHECK(std::holds_alternative<Expected>(calls[index]));
++index;
}(),
...);
}
} // namespace
class ServerImplTest {
public:
net::MockServerMessageHandler local;
net::MockClientMessageQueue queue;
wpi::MockLogger logger;
server::ServerImpl server{logger};
};
TEST_F(ServerImplTest, AddClient) {
::testing::StrictMock<net::MockWireConnection> wire;
// EXPECT_CALL(wire, Flush());
MockSetPeriodicFunc setPeriodic;
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest AddClient",
"[ntcore][server]") {
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
EXPECT_EQ(name, "test@1");
EXPECT_NE(id, -1);
CHECK(name == "test@1");
CHECK(id != -1);
}
TEST_F(ServerImplTest, AddDuplicateClient) {
::testing::StrictMock<net::MockWireConnection> wire1;
::testing::StrictMock<net::MockWireConnection> wire2;
MockSetPeriodicFunc setPeriodic1;
MockSetPeriodicFunc setPeriodic2;
// EXPECT_CALL(wire1, Flush());
// EXPECT_CALL(wire2, Flush());
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest AddDuplicateClient",
"[ntcore][server]") {
net::MockWireConnection wire1;
net::MockWireConnection wire2;
SetPeriodicRecorder setPeriodic1;
SetPeriodicRecorder setPeriodic2;
auto [name1, id1] = server.AddClient("test", "connInfo", false, wire1,
setPeriodic1.AsStdFunction());
auto [name2, id2] = server.AddClient("test", "connInfo2", false, wire2,
setPeriodic2.AsStdFunction());
EXPECT_EQ(name1, "test@1");
EXPECT_NE(id1, -1);
EXPECT_EQ(name2, "test@2");
EXPECT_NE(id1, id2);
EXPECT_NE(id2, -1);
CHECK(name1 == "test@1");
CHECK(id1 != -1);
CHECK(name2 == "test@2");
CHECK(id1 != id2);
CHECK(id2 != -1);
}
TEST_F(ServerImplTest, AddClient3) {}
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest AddClient3",
"[ntcore][server]") {}
template <typename T>
static std::string EncodeText1(const T& msg) {
@@ -145,66 +186,24 @@ static std::vector<uint8_t> EncodeServerBinary(const T& msgs) {
return data;
}
TEST_F(ServerImplTest, PublishLocal) {
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest PublishLocal",
"[ntcore][server]") {
// publish before client connect
server.SetLocal(&local, &queue);
constexpr int pubuid = 1;
constexpr int pubuid2 = 2;
constexpr int pubuid3 = 3;
{
::testing::InSequence seq;
EXPECT_CALL(
local,
ServerAnnounce(std::string_view{"test"}, 0, std::string_view{"double"},
wpi::util::json::object(), std::optional<int>{pubuid}));
EXPECT_CALL(
local,
ServerAnnounce(std::string_view{"test2"}, 0, std::string_view{"double"},
wpi::util::json::object(), std::optional<int>{pubuid2}));
EXPECT_CALL(
local,
ServerAnnounce(std::string_view{"test3"}, 0, std::string_view{"double"},
wpi::util::json::object(), std::optional<int>{pubuid3}));
}
{
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubuid, "test", "double", wpi::util::json::object(), {}}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
// client connect; it should get already-published topic as soon as it
// subscribes
::testing::StrictMock<net::MockWireConnection> wire;
MockSetPeriodicFunc setPeriodic;
EXPECT_CALL(wire, GetVersion()).WillRepeatedly(Return(0x0401));
{
::testing::InSequence seq;
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // AddClient()
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // ClientSubscribe()
EXPECT_CALL(wire, GetLastReceivedTime()).WillOnce(Return(0));
EXPECT_CALL(wire, SendPing(100));
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
EXPECT_CALL(
wire,
DoWriteText(StrEq(EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test", 3, "double", std::nullopt, wpi::util::json::object()}}))))
.WillOnce(Return(0));
EXPECT_CALL(
wire,
DoWriteText(StrEq(EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test2", 8, "double", std::nullopt, wpi::util::json::object()}}))))
.WillOnce(Return(0));
EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // SendControl()
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
EXPECT_CALL(
wire,
DoWriteText(StrEq(EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test3", 11, "double", std::nullopt, wpi::util::json::object()}}))))
.WillOnce(Return(0));
EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // SendControl()
}
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
@@ -220,7 +219,7 @@ TEST_F(ServerImplTest, PublishLocal) {
{
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubuid2, "test2", "double", wpi::util::json::object(), {}}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
server.SendAllOutgoing(100, false);
@@ -229,56 +228,65 @@ TEST_F(ServerImplTest, PublishLocal) {
{
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubuid3, "test3", "double", wpi::util::json::object(), {}}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
server.SendAllOutgoing(200, false);
CheckServerMessageCounts(local, {.announce = 3});
CheckCallOrder<net::MockServerMessageHandler::AnnounceCall,
net::MockServerMessageHandler::AnnounceCall,
net::MockServerMessageHandler::AnnounceCall>(local.calls);
CHECK(local.announceCalls[0].name == "test");
CHECK(local.announceCalls[0].pubuid == std::optional<int>{pubuid});
CHECK(local.announceCalls[1].name == "test2");
CHECK(local.announceCalls[1].pubuid == std::optional<int>{pubuid2});
CHECK(local.announceCalls[2].name == "test3");
CHECK(local.announceCalls[2].pubuid == std::optional<int>{pubuid3});
REQUIRE(setPeriodic.calls.size() == 1u);
CHECK(setPeriodic.calls[0] == 100u);
CheckWireConnectionCounts(wire, {.sendPing = 1,
.ready = 2,
.writeText = 3,
.flush = 2,
.lastReceivedTime = 1});
CHECK(wire.sendPingCalls == std::vector<uint64_t>{100});
CheckCallOrder<
net::MockWireConnection::GetLastReceivedTimeCall,
net::MockWireConnection::SendPingCall, net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteTextCall,
net::MockWireConnection::WriteTextCall,
net::MockWireConnection::FlushCall, net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteTextCall,
net::MockWireConnection::FlushCall>(wire.calls);
CHECK(wire.writeTextCalls[0] ==
EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test", 3, "double", std::nullopt, wpi::util::json::object()}}));
CHECK(wire.writeTextCalls[1] ==
EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test2", 8, "double", std::nullopt, wpi::util::json::object()}}));
CHECK(wire.writeTextCalls[2] ==
EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test3", 11, "double", std::nullopt, wpi::util::json::object()}}));
}
TEST_F(ServerImplTest, ClientSubTopicOnlyThenValue) {
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest ClientSubTopicOnlyThenValue",
"[ntcore][server]") {
// publish before client connect
server.SetLocal(&local, &queue);
constexpr int pubuid = 1;
EXPECT_CALL(
local,
ServerAnnounce(std::string_view{"test"}, 0, std::string_view{"double"},
wpi::util::json::object(), std::optional<int>{pubuid}));
{
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubuid, "test", "double", wpi::util::json::object(), {}}});
queue.msgs.emplace_back(net::ClientMessage{
net::ClientValueMsg{pubuid, Value::MakeDouble(1.0, 10)}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
::testing::StrictMock<net::MockWireConnection> wire;
EXPECT_CALL(wire, GetVersion()).WillRepeatedly(Return(0x0401));
MockSetPeriodicFunc setPeriodic;
{
::testing::InSequence seq;
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // AddClient()
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // ClientSubscribe()
EXPECT_CALL(wire, GetLastReceivedTime()).WillOnce(Return(0));
EXPECT_CALL(wire, SendPing(100));
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
EXPECT_CALL(
wire,
DoWriteText(StrEq(EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test", 3, "double", std::nullopt, wpi::util::json::object()}}))))
.WillOnce(Return(0));
EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // SendValues()
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // ClientSubscribe()
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
EXPECT_CALL(
wire,
DoWriteBinary(wpi::util::SpanEq(EncodeServerBinary1(net::ServerMessage{
net::ServerValueMsg{3, Value::MakeDouble(1.0, 10)}}))))
.WillOnce(Return(0));
EXPECT_CALL(wire, Flush()); // SendValues()
}
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
// connect client
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
@@ -307,53 +315,56 @@ TEST_F(ServerImplTest, ClientSubTopicOnlyThenValue) {
}
server.SendOutgoing(id, 200);
CheckServerMessageCounts(local, {.announce = 1});
CheckCallOrder<net::MockServerMessageHandler::AnnounceCall>(local.calls);
CHECK(local.announceCalls[0].name == "test");
CHECK(local.announceCalls[0].pubuid == std::optional<int>{pubuid});
CHECK(setPeriodic.calls == std::vector<uint32_t>{100, 100});
CheckWireConnectionCounts(wire, {.sendPing = 1,
.ready = 2,
.writeText = 1,
.writeBinary = 1,
.flush = 2,
.lastReceivedTime = 1});
CHECK(wire.sendPingCalls == std::vector<uint64_t>{100});
CheckCallOrder<
net::MockWireConnection::GetLastReceivedTimeCall,
net::MockWireConnection::SendPingCall, net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteTextCall,
net::MockWireConnection::FlushCall, net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteBinaryCall,
net::MockWireConnection::FlushCall>(wire.calls);
CHECK(wire.writeTextCalls[0] ==
EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test", 3, "double", std::nullopt, wpi::util::json::object()}}));
CHECK(wire.writeBinaryCalls[0] ==
EncodeServerBinary1(net::ServerMessage{
net::ServerValueMsg{3, Value::MakeDouble(1.0, 10)}}));
}
TEST_F(ServerImplTest, ClientDisconnectUnpublish) {
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest ClientDisconnectUnpublish",
"[ntcore][server]") {
server.SetLocal(&local, &queue);
constexpr int pubuidLocal = 1;
constexpr int subuid = 1;
{
::testing::InSequence seq;
EXPECT_CALL(local, ServerAnnounce(std::string_view{"test2"}, 0,
std::string_view{"double"},
wpi::util::json::object(),
std::optional<int>{pubuidLocal}));
EXPECT_CALL(
local,
ServerAnnounce(std::string_view{"test"}, 0, std::string_view{"double"},
wpi::util::json::object(), std::optional<int>{}));
EXPECT_CALL(local, ServerUnannounce(std::string_view{"test"}, 0));
}
{
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubuidLocal, "test2", "double", wpi::util::json::object(), {}}});
queue.msgs.emplace_back(net::ClientMessage{
net::ClientValueMsg{pubuidLocal, Value::MakeDouble(1.0, 10)}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
{
queue.msgs.emplace_back(
net::ClientMessage{net::SubscribeMsg{subuid, {"test"}, {}}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
::testing::StrictMock<net::MockWireConnection> wire;
EXPECT_CALL(wire, GetVersion()).WillRepeatedly(Return(0x0401));
MockSetPeriodicFunc setPeriodic;
{
::testing::InSequence seq;
EXPECT_CALL(wire, GetLastReceivedTime()).WillOnce(Return(0));
EXPECT_CALL(wire, SendPing(100));
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
EXPECT_CALL(
wire, DoWriteText(StrEq(EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test", 8, "double", 1, wpi::util::json::object()}}))))
.WillOnce(Return(0));
EXPECT_CALL(wire, Flush()); // SendValues()
}
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
// connect client
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
@@ -372,9 +383,35 @@ TEST_F(ServerImplTest, ClientDisconnectUnpublish) {
// disconnect client
server.RemoveClient(id);
CheckServerMessageCounts(local, {.announce = 2, .unannounce = 1});
CheckCallOrder<net::MockServerMessageHandler::AnnounceCall,
net::MockServerMessageHandler::AnnounceCall,
net::MockServerMessageHandler::UnannounceCall>(local.calls);
CHECK(local.announceCalls[0].name == "test2");
CHECK(local.announceCalls[0].pubuid == std::optional<int>{pubuidLocal});
CHECK(local.announceCalls[1].name == "test");
CHECK(local.announceCalls[1].pubuid == std::optional<int>{});
CHECK(local.unannounceCalls[0].name == "test");
CHECK(local.unannounceCalls[0].id == 0);
CheckWireConnectionCounts(wire, {.sendPing = 1,
.ready = 1,
.writeText = 1,
.flush = 1,
.lastReceivedTime = 1});
CHECK(wire.sendPingCalls == std::vector<uint64_t>{100});
CheckCallOrder<net::MockWireConnection::GetLastReceivedTimeCall,
net::MockWireConnection::SendPingCall,
net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteTextCall,
net::MockWireConnection::FlushCall>(wire.calls);
CHECK(wire.writeTextCalls[0] ==
EncodeText1(net::ServerMessage{net::AnnounceMsg{
"test", 8, "double", 1, wpi::util::json::object()}}));
}
TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest ZeroTimestampNegativeTime",
"[ntcore][server]") {
// publish before client connect
server.SetLocal(&local, &queue);
constexpr int pubuid = 1;
@@ -384,16 +421,7 @@ TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
defaultValue.SetTime(0);
defaultValue.SetServerTime(0);
Value value = Value::MakeDouble(5, -10);
{
::testing::InSequence seq;
EXPECT_CALL(
local,
ServerAnnounce(std::string_view{"test"}, 0, std::string_view{"double"},
wpi::util::json::object(), std::optional<int>{pubuid}))
.WillOnce(Return(topicHandle));
EXPECT_CALL(local, ServerSetValue(topicHandle, defaultValue));
EXPECT_CALL(local, ServerSetValue(topicHandle, value));
}
local.announceReturns.emplace_back(topicHandle);
{
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
@@ -402,17 +430,13 @@ TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
net::ClientMessage{net::ClientValueMsg{pubuid, defaultValue}});
queue.msgs.emplace_back(
net::ClientMessage{net::SubscribeMsg{subuid, {"test"}, {}}});
EXPECT_FALSE(server.ProcessLocalMessages(UINT_MAX));
CHECK_FALSE(server.ProcessLocalMessages(UINT_MAX));
}
// client connect; it should get already-published topic as soon as it
// subscribes
::testing::StrictMock<net::MockWireConnection> wire;
MockSetPeriodicFunc setPeriodic;
{
::testing::InSequence seq;
// EXPECT_CALL(wire, Flush()); // AddClient()
}
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
@@ -427,6 +451,17 @@ TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
msgs.emplace_back(net::ClientMessage{net::ClientValueMsg{pubuid2, value}});
server.ProcessIncomingBinary(id, EncodeServerBinary(msgs));
}
CheckServerMessageCounts(local, {.announce = 1, .setValue = 2});
CheckCallOrder<net::MockServerMessageHandler::AnnounceCall,
net::MockServerMessageHandler::SetValueCall,
net::MockServerMessageHandler::SetValueCall>(local.calls);
CHECK(local.announceCalls[0].name == "test");
CHECK(local.announceCalls[0].pubuid == std::optional<int>{pubuid});
CHECK(local.setValueCalls[0].topicuid == topicHandle);
CHECK(local.setValueCalls[0].value == defaultValue);
CHECK(local.setValueCalls[1].topicuid == topicHandle);
CHECK(local.setValueCalls[1].value == value);
}
// When a client re-subscribes with a shorter period for a topic it already
@@ -449,37 +484,29 @@ TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
// DoWriteBinary called once.
// With bug: SetPeriod not called → value stays in 200ms queue
// (nextSendMs=200) → DoWriteBinary NOT called at t=150.
TEST_F(ServerImplTest, ResubscribeShorterPeriodUpdatesTopicOutgoing) {
TEST_CASE_METHOD(ServerImplTest,
"ServerImplTest ResubscribeShorterPeriodUpdatesTopicOutgoing",
"[ntcore][server]") {
server.SetLocal(&local, &queue);
constexpr int pubuid = 1;
constexpr int subuid = 1;
EXPECT_CALL(local, ServerAnnounce(std::string_view{"test"}, _, _, _, _))
.WillOnce(Return(0));
EXPECT_CALL(local, ServerSetValue(_, _)).Times(::testing::AnyNumber());
// Publish topic and initial value so there is a lastValue when the client
// subscribes.
queue.msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubuid, "test", "double", wpi::util::json::object(), {}}});
queue.msgs.emplace_back(net::ClientMessage{
net::ClientValueMsg{pubuid, Value::MakeDouble(1.0, 50)}});
ASSERT_FALSE(server.ProcessLocalMessages(UINT_MAX));
REQUIRE_FALSE(server.ProcessLocalMessages(UINT_MAX));
::testing::NiceMock<net::MockWireConnection> wire;
EXPECT_CALL(wire, GetVersion()).WillRepeatedly(Return(0x0401));
MockSetPeriodicFunc setPeriodic;
EXPECT_CALL(setPeriodic, Call(_)).Times(::testing::AnyNumber());
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
int binaryCallCount = 0;
ON_CALL(wire, DoWriteBinary(_)).WillByDefault([&](std::span<const uint8_t>) {
wire.onWriteBinary = [&](std::span<const uint8_t>) {
++binaryCallCount;
return 0;
});
ON_CALL(wire, Ready()).WillByDefault(Return(true));
ON_CALL(wire, DoWriteText(_)).WillByDefault(Return(0));
ON_CALL(wire, Flush()).WillByDefault(Return(0));
ON_CALL(wire, GetLastReceivedTime()).WillByDefault(Return(0));
};
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
@@ -517,20 +544,35 @@ TEST_F(ServerImplTest, ResubscribeShorterPeriodUpdatesTopicOutgoing) {
binaryCallCount = 0;
server.SendOutgoing(id, 150);
EXPECT_EQ(binaryCallCount, 1)
<< "Re-subscribing with a shorter period must update the topic's "
"outgoing queue period; the re-queued last value should be sent by "
"t=150ms (XOR bug: UpdatePeriod is skipped when added && removed are "
"both true, leaving the topic mapped to the 200ms queue)";
UNSCOPED_INFO(
"Re-subscribing with a shorter period must update the topic's outgoing "
"queue period; the re-queued last value should be sent by t=150ms (XOR "
"bug: UpdatePeriod is skipped when added && removed are both true, "
"leaving the topic mapped to the 200ms queue)");
CHECK(binaryCallCount == 1);
CheckWireConnectionCounts(wire, {.sendPing = 1,
.ready = 2,
.writeText = 1,
.writeBinary = 2,
.flush = 2,
.lastReceivedTime = 1});
CheckCallOrder<
net::MockWireConnection::GetLastReceivedTimeCall,
net::MockWireConnection::SendPingCall, net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteTextCall,
net::MockWireConnection::WriteBinaryCall,
net::MockWireConnection::FlushCall, net::MockWireConnection::ReadyCall,
net::MockWireConnection::WriteBinaryCall,
net::MockWireConnection::FlushCall>(wire.calls);
}
TEST_F(ServerImplTest, InvalidPubUid) {
EXPECT_CALL(logger, Call(_, _, _, "0: pubuid out of range"));
TEST_CASE_METHOD(ServerImplTest, "ServerImplTest InvalidPubUid",
"[ntcore][server]") {
server.SetLocal(&local, &queue);
// connect client
::testing::StrictMock<net::MockWireConnection> wire;
MockSetPeriodicFunc setPeriodic;
net::MockWireConnection wire;
SetPeriodicRecorder setPeriodic;
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
@@ -538,6 +580,8 @@ TEST_F(ServerImplTest, InvalidPubUid) {
id,
"[{\"method\":\"publish\",\"params\":{\"type\":\"string\",\"name\":"
"\"myvalue\",\"pubuid\":2147483647,\"properties\":{}}}]");
logger.CheckMessage(NT_LOG_WARNING, "0: pubuid out of range");
CheckNoServerCalls(local);
}
} // namespace wpi::nt