From bfc12a1aea2da846a266ccaa40dc7919fc042786 Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 3 May 2026 12:23:19 +1000 Subject: [PATCH] OSC tests --- CMakeLists.txt | 19 ++ .../OscServer.h | 2 + tests/OscServerTests.cpp | 176 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 tests/OscServerTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e0d23a..572b1c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,25 @@ endif() add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests) +add_executable(OscServerTests + "${APP_DIR}/OscServer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/OscServerTests.cpp" +) + +target_include_directories(OscServerTests PRIVATE + "${APP_DIR}" +) + +target_link_libraries(OscServerTests PRIVATE + Ws2_32 +) + +if(MSVC) + target_compile_options(OscServerTests PRIVATE /W3) +endif() + +add_test(NAME OscServerTests COMMAND OscServerTests) + add_custom_command(TARGET LoopThroughWithOpenGLCompositing POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GPUDIRECT_DIR}/bin/x64/dvp.dll" diff --git a/apps/LoopThroughWithOpenGLCompositing/OscServer.h b/apps/LoopThroughWithOpenGLCompositing/OscServer.h index 945ffc9..da55959 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OscServer.h +++ b/apps/LoopThroughWithOpenGLCompositing/OscServer.h @@ -26,6 +26,8 @@ public: unsigned short GetPort() const { return mPort; } private: + friend struct OscServerTestAccess; + struct OscMessage { std::string address; diff --git a/tests/OscServerTests.cpp b/tests/OscServerTests.cpp new file mode 100644 index 0000000..77c731c --- /dev/null +++ b/tests/OscServerTests.cpp @@ -0,0 +1,176 @@ +#include "OscServer.h" + +#include +#include +#include +#include +#include + +namespace +{ +int gFailures = 0; + +void Expect(bool condition, const char* message) +{ + if (condition) + return; + + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} + +void AppendPaddedString(std::vector& packet, const std::string& text) +{ + packet.insert(packet.end(), text.begin(), text.end()); + packet.push_back('\0'); + while (packet.size() % 4 != 0) + packet.push_back('\0'); +} + +void AppendInt32(std::vector& packet, int value) +{ + const unsigned int bits = static_cast(value); + packet.push_back(static_cast((bits >> 24) & 0xff)); + packet.push_back(static_cast((bits >> 16) & 0xff)); + packet.push_back(static_cast((bits >> 8) & 0xff)); + packet.push_back(static_cast(bits & 0xff)); +} + +void AppendFloat32(std::vector& packet, float value) +{ + unsigned int bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + AppendInt32(packet, static_cast(bits)); +} + +std::vector BuildOscPacket(const std::string& address, const std::string& typeTags) +{ + std::vector packet; + AppendPaddedString(packet, address); + AppendPaddedString(packet, typeTags); + return packet; +} +} + +struct OscServerTestAccess +{ + using Message = OscServer::OscMessage; + + static bool Decode(OscServer& server, const std::vector& packet, Message& message, std::string& error) + { + return server.DecodeMessage(packet.data(), static_cast(packet.size()), message, error); + } + + static bool Dispatch(OscServer& server, const Message& message, std::string& error) + { + return server.DispatchMessage(message, error); + } + + static void SetUpdateParameterCallback( + OscServer& server, + const std::function& callback) + { + server.mCallbacks.updateParameter = callback; + } +}; + +namespace +{ +void TestDecodeFloatMessage() +{ + OscServer server; + std::vector packet = BuildOscPacket("/VideoShaderToys/VHS/intensity", ",f"); + AppendFloat32(packet, 0.75f); + + OscServerTestAccess::Message message; + std::string error; + Expect(OscServerTestAccess::Decode(server, packet, message, error), "float OSC message decodes"); + Expect(message.address == "/VideoShaderToys/VHS/intensity", "float OSC address is preserved"); + Expect(message.valueJson.find("0.75") == 0, "float OSC value becomes JSON number"); +} + +void TestDecodeIntStringAndBoolMessages() +{ + OscServer server; + + std::vector intPacket = BuildOscPacket("/VideoShaderToys/layer-1/mode", ",i"); + AppendInt32(intPacket, 3); + OscServerTestAccess::Message intMessage; + std::string error; + Expect(OscServerTestAccess::Decode(server, intPacket, intMessage, error), "int OSC message decodes"); + Expect(intMessage.valueJson == "3", "int OSC value becomes JSON number"); + + std::vector stringPacket = BuildOscPacket("/VideoShaderToys/layer-1/mode", ",s"); + AppendPaddedString(stringPacket, "equisolid"); + OscServerTestAccess::Message stringMessage; + error.clear(); + Expect(OscServerTestAccess::Decode(server, stringPacket, stringMessage, error), "string OSC message decodes"); + Expect(stringMessage.valueJson == "\"equisolid\"", "string OSC value becomes JSON string"); + + std::vector boolPacket = BuildOscPacket("/VideoShaderToys/layer-1/enabled", ",T"); + OscServerTestAccess::Message boolMessage; + error.clear(); + Expect(OscServerTestAccess::Decode(server, boolPacket, boolMessage, error), "boolean OSC message decodes"); + Expect(boolMessage.valueJson == "true", "true OSC typetag becomes JSON boolean"); +} + +void TestDispatchValidAddress() +{ + OscServer server; + std::string layerKey; + std::string parameterKey; + std::string valueJson; + OscServerTestAccess::SetUpdateParameterCallback(server, [&](const std::string& layer, const std::string& parameter, const std::string& value, std::string&) + { + layerKey = layer; + parameterKey = parameter; + valueJson = value; + return true; + }); + + OscServerTestAccess::Message message; + message.address = "/VideoShaderToys/VHS/intensity"; + message.valueJson = "0.5"; + std::string error; + Expect(OscServerTestAccess::Dispatch(server, message, error), "valid OSC control address dispatches"); + Expect(layerKey == "VHS", "dispatch extracts layer key"); + Expect(parameterKey == "intensity", "dispatch extracts parameter key"); + Expect(valueJson == "0.5", "dispatch forwards JSON value"); +} + +void TestRejectsUnsupportedAddress() +{ + OscServer server; + bool called = false; + OscServerTestAccess::SetUpdateParameterCallback(server, [&](const std::string&, const std::string&, const std::string&, std::string&) + { + called = true; + return true; + }); + + OscServerTestAccess::Message message; + message.address = "/OtherApp/VHS/intensity"; + message.valueJson = "0.5"; + std::string error; + Expect(!OscServerTestAccess::Dispatch(server, message, error), "unsupported OSC namespace is rejected"); + Expect(!called, "unsupported OSC namespace does not invoke callback"); + Expect(!error.empty(), "unsupported OSC address reports an error"); +} +} + +int main() +{ + TestDecodeFloatMessage(); + TestDecodeIntStringAndBoolMessages(); + TestDispatchValidAddress(); + TestRejectsUnsupportedAddress(); + + if (gFailures != 0) + { + std::cerr << gFailures << " OscServer test failure(s).\n"; + return 1; + } + + std::cout << "OscServer tests passed.\n"; + return 0; +}