mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-03 03:01:44 +00:00
[ntcore] Translate unit tests to catch2 (#8997)
Use manual mocks instead of googlemock.
This commit is contained in:
@@ -190,7 +190,7 @@ cc_test(
|
||||
],
|
||||
deps = [
|
||||
":ntcore",
|
||||
"//thirdparty/googletest",
|
||||
"//thirdparty/catch2",
|
||||
"//wpiutil:wpiutil-testlib",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
191
ntcore/src/test/native/cpp/MockAssertions.hpp
Normal file
191
ntcore/src/test/native/cpp/MockAssertions.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user